netcdf 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 81d6a201281ded6b844b92d6d66413b81aa9c89c3b2faff4c76d783fdefea7d3
4
+ data.tar.gz: bbe34f91b073bfb82daa5825dd1826c19b6d02cfce585e8df919170207749ea6
5
+ SHA512:
6
+ metadata.gz: 3fdb1a510287e5f1b886ae66cadf6864a5efaa896452f4f9aa158bb88ccef40b6fef6a0c8dff536cef464b6db7360bc800b64a7656d6c53f8d47223c0ffccee1
7
+ data.tar.gz: 3dd9e164eefbec8f3868deac541ace703b831fa67bd390319be2073af12bf31937622ef24342fc5344a302e2df7607f5978597e393363a9d45347eb500f51c85
data/CHANGELOG.md ADDED
@@ -0,0 +1,34 @@
1
+ # Changelog
2
+
3
+ Releases are recorded here from 0.1.0, which is the first. There is no
4
+ separate NEWS file: this is where to look for what changed between the
5
+ version you have and a newer one.
6
+
7
+ <!-- Newest first, at both levels: a new release section goes above the
8
+ ones below it, and a new entry goes directly under its own release
9
+ heading -- not at the end of the section. The kind of change is
10
+ carried by the `- Fix:` / `- Change:` / `- New:` that opens the
11
+ entry; there are no per-kind subheadings.
12
+
13
+ A section is written newest-first while the release is open, and
14
+ sorted into New, Change, Fix when it closes -- in the same commit
15
+ that drops `(unreleased)`. Within Change, the ones that ask the
16
+ reader to change code come first.
17
+
18
+ An entry says three things and stops: what changed, what to do about
19
+ it (the migration, the replacement, the condition under which nothing
20
+ changes), and what is excluded. It does not say how the code was
21
+ broken or name the internals that were fixed -- those belong in the
22
+ commit message. Two to six lines.
23
+
24
+ It is written for someone using the library, not someone working on
25
+ it: with no NEWS file, this is what a reader consults before
26
+ upgrading.
27
+
28
+ The version here is this gem's own and is not libnetcdf's. Which
29
+ libnetcdf a release needs is said in extconf.rb, and an entry says
30
+ so only when the answer changes. -->
31
+
32
+ ## 0.1.0
33
+
34
+ First release.
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 himotoyoshi
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,82 @@
1
+ # netcdf
2
+
3
+ A Ruby binding to the [NetCDF](https://www.unidata.ucar.edu/software/netcdf/) C library that passes data through Ruby's MemoryView protocol. An array library's own memory is handed to libnetcdf, which reads or writes it where it lies: nothing is copied, and no Ruby Array stands in between.
4
+
5
+ The binding is nearly one to one, for code that wants to be written the way NetCDF itself is written; [Unidata's C documentation](https://docs.unidata.ucar.edu/netcdf-c/current/) reads as documentation for it.
6
+
7
+ ## Features
8
+
9
+ * Programs are written the way NetCDF's own are: every `nc_*` function under its own name, taking its arguments in its own order, so a call in a C example is written here as it stands
10
+ * Any MemoryView producer is a buffer, with no adapter written for it, and libnetcdf reads or writes its memory where it lies
11
+ * Array I/O releases the GVL, so other threads keep running through a long read or write; netCDF calls themselves are serialized, since libnetcdf is not thread-safe
12
+
13
+ What libnetcdf offers is bound and covered by the test suite, apart from a handful of features listed at the end of the [reference](docs/03_reference.md#not-yet-implemented).
14
+
15
+ ## Install
16
+
17
+ Install libnetcdf and its headers first, then the gem:
18
+
19
+ ```
20
+ gem install netcdf
21
+ ```
22
+
23
+ Or add it to your `Gemfile`:
24
+
25
+ ```ruby
26
+ gem "netcdf"
27
+ ```
28
+
29
+ Requires:
30
+
31
+ - Ruby >= 3.1
32
+ - libnetcdf >= 4.1, with its headers (`nc-config` or `pkg-config` on PATH)
33
+ - A C compiler
34
+
35
+ ## Example
36
+
37
+ Arrays are passed to and from this gem through [MemoryView](https://github.com/ruby/ruby/blob/master/include/ruby/memory_view.h), the protocol Ruby 3.0 added for sharing an array's memory between libraries. An array library that implements it works here as it is, with no adapter written on either side; the gem itself depends on no array library. The example below takes [CArray](https://github.com/himotoyoshi/carray) as one such library: it writes a 3 × 4 variable with two attributes, and reads it back.
38
+
39
+ ```ruby
40
+ require "netcdf"
41
+ require "carray" # a MemoryView producer/consumer
42
+
43
+ ncid = NetCDF.nc_create("out.nc", NetCDF::NC_CLOBBER | NetCDF::NC_NETCDF4)
44
+
45
+ y = NetCDF.nc_def_dim(ncid, "y", 3)
46
+ x = NetCDF.nc_def_dim(ncid, "x", 4)
47
+ v = NetCDF.nc_def_var(ncid, "T", NetCDF::NC_DOUBLE, [y, x])
48
+
49
+ NetCDF.nc_put_att(ncid, v, "units", "K") # type inferred from the value
50
+ NetCDF.nc_put_att(ncid, v, "valid_range", [200.0, 350.0])
51
+
52
+ NetCDF.nc_enddef(ncid)
53
+
54
+ src = CArray.double(3, 4).seq(1.0)
55
+ NetCDF.nc_put_var(ncid, v, src)
56
+
57
+ dst = CArray.double(3, 4) # a read goes into memory you provide
58
+ NetCDF.nc_get_vara(ncid, v, [0, 0], [3, 4], dst)
59
+
60
+ NetCDF.nc_close(ncid)
61
+ ```
62
+
63
+ libnetcdf reads and writes the array's own memory, with nothing copied in between.
64
+
65
+ ## Documentation
66
+
67
+ * [Introduction](docs/00_introduction.md) — what this gem is, what it leaves to a layer above, and what is not implemented
68
+ * [Getting started](docs/01_getting_started.md) — install, the shape of the API, writing and reading your first file
69
+ * [Buffers and MemoryView](docs/02_buffers.md) — how data actually moves: what a buffer must satisfy, format strings, and handing an array library's memory straight over
70
+ * [Reference](docs/03_reference.md) — every function, grouped by subsystem, plus every constant
71
+
72
+ ## Contributing
73
+
74
+ Bug reports, feature requests and pull requests are welcome.
75
+
76
+ ## Credits
77
+
78
+ netcdf was designed and reviewed by a human developer; the implementation was produced in collaboration with AI coding tools.
79
+
80
+ ## License
81
+
82
+ MIT
@@ -0,0 +1,34 @@
1
+ # Introduction
2
+
3
+ netcdf is a Ruby binding to the [NetCDF](https://www.unidata.ucar.edu/software/netcdf/) C library. It takes NetCDF itself as known: what this guide has to explain is the Ruby side of the boundary — how a `nc_*` function reads as a Ruby call, what it returns, what it raises, and how bulk data crosses without being copied.
4
+
5
+ ## A literal translation of libnetcdf
6
+
7
+ The gem translates libnetcdf into Ruby word for word. Each `nc_*` function is a `NetCDF.nc_*` method with the same name, the same arguments in the same order, and the same meaning. Only the grammar changes: what C returns through a pointer argument comes back as a Ruby value, and what C reports as a non-zero status is raised. The vocabulary does not — nothing is grouped, renamed, or given a friendlier spelling — so a call you read in C documentation, in an example, or in someone's Fortran-shaped code is written here as it stands. The few places where a Ruby call does more than its C counterpart — `nc_put_att` taking the type from the value, an `NC_STRING` read returning Ruby Strings — are marked in the [reference](03_reference.md) where they occur.
8
+
9
+ Translating rather than redesigning settles three things:
10
+
11
+ - **Ids stay the integers libnetcdf uses.** `ncid`, `dimid`, `varid` and `grpid` are passed to you and back unchanged, so a file's identity is the same value on both sides of the boundary and yours to keep wherever you like.
12
+ - **Data stays where your array library holds it.** A buffer here is a block of memory the library reads or writes in place; element access, arithmetic, slicing and type coercion stay with the array class that owns the data.
13
+ - **Attributes are stored and returned verbatim.** `_FillValue`, `scale_factor`, `add_offset` and CF time come back exactly as they were written — a convention is yours to apply, at the point where you know which one the file follows.
14
+
15
+ A translation this close can be checked against the C API function by function, and it never stands between you and a feature it was not designed for. The conveniences — an object-oriented surface, masks, CF/COARDS decoding — belong to a layer written above this one.
16
+
17
+ The practical consequence is that [Unidata's C documentation](https://docs.unidata.ucar.edu/netcdf-c/current/) is documentation for this gem. When you want to know what `nc_def_var_chunking` does to a file, that is where the answer is; what this guide covers is what the Ruby call returns, what it raises, and how the buffer works.
18
+
19
+ ## Passing data through the MemoryView protocol
20
+
21
+ Metadata crosses as ordinary Ruby values: names are Strings, ids and lengths are Integers, an attribute goes in as a Ruby value and comes back as one. Bulk numeric data does not — it is handed over through Ruby's MemoryView protocol, which is what lets a whole array cross the boundary as memory rather than as a converted Ruby object.
22
+
23
+ A buffer, in this gem, is any object that exposes MemoryView: the gem asks it for the pointer, checks that the memory is row-major contiguous and that its format agrees with the variable, and lets libnetcdf read or write it where it lies. The gem copies nothing in either direction — what libnetcdf does with the bytes after that, type conversion included, is libnetcdf's business — and the call runs with the GVL released. An array class that exposes the protocol is therefore usable as a buffer with no adapter written for it.
24
+
25
+ The gem allocates no buffers of its own: a `get` reads into memory you hand it, as a `put` writes from it. The gem depends on no array library; this guide's examples take CArray as one that implements MemoryView.
26
+
27
+ ## What is not implemented
28
+
29
+ - Compound types
30
+ - Vlen variable I/O (defining and inquiring vlen types works)
31
+ - Parallel I/O
32
+ - DAP / pNetCDF backend tuning, log control, and other rarely used knobs
33
+
34
+ These are deferred rather than out of scope. The function names are listed at the end of the [reference](03_reference.md#not-yet-implemented).
@@ -0,0 +1,127 @@
1
+ # Getting started
2
+
3
+ This page goes from an empty directory to a NetCDF file written and read back. NetCDF itself is taken as known; what is shown is how a `nc_*` call is written in Ruby, and which of its edges are Ruby's rather than C's.
4
+
5
+ ## Install
6
+
7
+ Install libnetcdf and its headers first, then the gem:
8
+
9
+ ```
10
+ gem install netcdf
11
+ ```
12
+
13
+ The build finds libnetcdf through `nc-config` or `pkg-config`, so whichever of those the install put on your `PATH` is what gets used. Check what you ended up with:
14
+
15
+ ```ruby
16
+ require "netcdf"
17
+ NetCDF.nc_inq_libvers # => "4.9.2 of Mar 15 2024 ..."
18
+ ```
19
+
20
+ The examples below use [CArray](https://github.com/himotoyoshi/carray), a multi-dimensional array library that can be both a producer and a consumer of MemoryView:
21
+
22
+ ```
23
+ gem install carray
24
+ ```
25
+
26
+ ## What is Ruby rather than C
27
+
28
+ `NetCDF.nc_open`, `NetCDF.nc_def_var`, `NetCDF.nc_put_vara`: the names and the argument order are the ones you already know. Four things are spelled Ruby's way instead of C's.
29
+
30
+ - **Status codes are exceptions.** A call that fails raises `NetCDF::Error`; there is no return value to check.
31
+ - **Output parameters are return values.** `nc_inq_var` hands back its five values as an Array instead of filling five pointers.
32
+ - **A lookup that finds nothing returns `nil`.** `nc_inq_varid` on a name that is not in the file, for one — absence is not an error.
33
+ - **Bulk data moves through MemoryView.** A buffer is memory libnetcdf reads or writes where it lies, so an array library's own storage is handed over as it stands.
34
+
35
+ ## Define mode and data mode
36
+
37
+ `nc_enddef` and `nc_redef` are here as they are in C, and the gem does nothing to hide the two phases — but how strictly they are enforced depends on the format, which is worth seeing before the first example. A classic (NetCDF-3) file refuses a write before `nc_enddef`:
38
+
39
+ ```
40
+ NetCDF::Error: NetCDF: Operation not allowed in define mode # status -39
41
+ ```
42
+
43
+ A NetCDF-4 file does not — libnetcdf switches modes for you there, and the write goes through. Call `nc_enddef` anyway, at the point where the schema is complete: it is what makes the same code work against both formats, and it marks the boundary for a reader.
44
+
45
+ Opening an existing file skips all of this — the file is already in data mode.
46
+
47
+ ## Writing a file
48
+
49
+ ```ruby
50
+ require "netcdf"
51
+ require "carray" # a MemoryView producer/consumer
52
+
53
+ ncid = NetCDF.nc_create("temp.nc", NetCDF::NC_CLOBBER | NetCDF::NC_NETCDF4)
54
+
55
+ # --- define mode ---
56
+ y = NetCDF.nc_def_dim(ncid, "y", 3)
57
+ x = NetCDF.nc_def_dim(ncid, "x", 4)
58
+
59
+ v = NetCDF.nc_def_var(ncid, "temperature", NetCDF::NC_DOUBLE, [y, x])
60
+
61
+ NetCDF.nc_put_att(ncid, v, "units", "K")
62
+ NetCDF.nc_put_att(ncid, v, "valid_range", [200.0, 350.0])
63
+ NetCDF.nc_put_att(ncid, NetCDF::NC_GLOBAL, "title", "example field")
64
+
65
+ NetCDF.nc_enddef(ncid)
66
+
67
+ # --- data mode ---
68
+ values = CArray.double(3, 4).seq(271.0)
69
+ NetCDF.nc_put_var(ncid, v, values)
70
+
71
+ NetCDF.nc_close(ncid)
72
+ ```
73
+
74
+ Four things in there are worth naming:
75
+
76
+ - Creation flags are OR-ed together as in C: `NC_CLOBBER | NC_NETCDF4` here. A constant is `NetCDF::NC_*`, and every one libnetcdf defines is in the [reference](03_reference.md).
77
+ - Dimension ids come back from `nc_def_dim` and go into `nc_def_var` as an Array, in the order the variable is indexed.
78
+ - `nc_put_att` picks the on-disk type from the Ruby value — `String` becomes `NC_CHAR`, `Float` becomes `NC_DOUBLE`, an Array of Floats becomes an `NC_DOUBLE` vector. When you need to pin the type down, the typed calls (`nc_put_att_float` and friends) are in the [reference](03_reference.md#typed-putget).
79
+ - The data goes in as a `CArray`, not a Ruby Array: libnetcdf reads the array's own memory. Any other MemoryView producer would do; that is the subject of the [next page](02_buffers.md).
80
+
81
+ ## Reading it back
82
+
83
+ ```ruby
84
+ require "netcdf"
85
+ require "carray" # a MemoryView producer/consumer
86
+
87
+ ncid = NetCDF.nc_open("temp.nc", NetCDF::NC_NOWRITE)
88
+
89
+ varid = NetCDF.nc_inq_varid(ncid, "temperature")
90
+ name, xtype, ndims, dimids, natts = NetCDF.nc_inq_var(ncid, varid)
91
+ shape = dimids.map { |d| NetCDF.nc_inq_dimlen(ncid, d) }
92
+ shape # => [3, 4]
93
+
94
+ NetCDF.nc_get_att(ncid, varid, "units") # => "K"
95
+
96
+ buf = CArray.double(*shape)
97
+ NetCDF.nc_get_var(ncid, varid, buf) # => buf, filled
98
+ buf[0, nil].to_a # => [271.0, 272.0, 273.0, 274.0]
99
+
100
+ NetCDF.nc_close(ncid)
101
+ ```
102
+
103
+ A `get` reads into the buffer you pass, as a `put` writes from it, and returns that buffer. The gem allocates none of its own: the shape comes from the dimensions, and the element type is yours to choose — libnetcdf converts from the variable's type to it. The one exception is an `NC_STRING` variable, which comes back as Ruby Strings and takes no buffer.
104
+
105
+ ## Reading part of a variable
106
+
107
+ `nc_get_vara` takes a corner and a shape as Ruby Arrays, zero-based as in C:
108
+
109
+ ```ruby
110
+ rows = CArray.double(2, 4)
111
+ NetCDF.nc_get_vara(ncid, varid, [1, 0], [2, 4], rows) # last two rows
112
+ ```
113
+
114
+ `nc_get_vars` adds a stride, and `nc_get_varm` a mapping vector for transposing on the way out. Each has a `put` twin taking the same arguments plus the buffer.
115
+
116
+ ## When something fails
117
+
118
+ ```ruby
119
+ begin
120
+ NetCDF.nc_open("missing.nc")
121
+ rescue NetCDF::Error => e
122
+ e.message # => "No such file or directory" — libnetcdf's own text
123
+ e.status # => 2 the raw libnetcdf status
124
+ end
125
+ ```
126
+
127
+ `#status` is what to match on when you care about a specific failure rather than any failure. NetCDF's own conditions are negative (`-39` is "operation not allowed in define mode"); a positive status is an errno the C library passed through, as above.
@@ -0,0 +1,95 @@
1
+ # Buffers and MemoryView
2
+
3
+ Every numeric value this gem reads or writes passes through a buffer, and a buffer is any object implementing Ruby's MemoryView protocol. That is the whole interop story: an array library that exposes MemoryView hands libnetcdf its own memory, and the gem copies nothing on the way in or out.
4
+
5
+ This page is that surface: what a buffer has to satisfy, what to pass when you have no array library, how a format string is read, and where an array library fits.
6
+
7
+ The gem allocates no buffers itself. A `get` reads into the buffer you pass and returns it, just as a `put` writes from one; leaving the buffer off raises `ArgumentError: buffer is required`. `NC_STRING` variables are the exception (see [the end of this page](#types-that-are-not-buffers)).
8
+
9
+ ## A buffer is a block of memory
10
+
11
+ Not a collection. A Ruby `Array` of Floats is a list of boxed objects; there is nothing in it to hand to a C function that wants twelve contiguous 8-byte values, and packing one is a copy plus a per-element conversion — work that belongs to whatever owns the data, not to this boundary.
12
+
13
+ A `String` *is* a block of bytes, but it carries no shape, no element type, and no writability guarantee, so the array I/O surface does not take one. Hand the bytes over through a producer instead — an array library's array, or a `Fiddle::Pointer` ([below](#without-an-array-library)).
14
+
15
+ Strings do appear elsewhere — `NC_CHAR` and `NC_STRING` values, opaque scalars, attribute text — because there the data really is text or an opaque blob. It is only the numeric array surface that refuses them.
16
+
17
+ ## What a buffer has to satisfy
18
+
19
+ - **A MemoryView producer.** Anything else raises `TypeError: buffer must provide a MemoryView`. Note that a library's view objects are often not producers even when its arrays are — a `CArray` is one, `CArray#transpose` is not.
20
+ - **Row-major (C-contiguous).** libnetcdf reads and writes a flat run of elements, so a strided or column-major view has no meaning at this boundary. A producer that hands over a strided view — including a 1-D one that gives strides without a shape — is refused with `ArgumentError: buffer must be row-major (C-contiguous)` rather than read wrongly. Make a contiguous copy first — `CArray#copy`, for a CArray view.
21
+ - **Large enough.** The element count implied by `count` (or by the whole variable) must fit, or `ArgumentError: buffer too small` says how many bytes were expected.
22
+ - **Writable, on a `get`.** A producer that refuses a writable view raises `TypeError: buffer must provide a writable MemoryView`. One that hands back a view marked read-only anyway — `rb_memory_view_get` does not hold a producer to the request — raises `TypeError: buffer is read-only`, rather than being written into. `Fiddle::Pointer` is one up to fiddle 1.1.8: it marks every view read-only, so it serves as the source of a `put` but not as the destination of a `get`. fiddle 1.1.9 hands out a writable view when asked.
23
+
24
+ The element *type* need not match the variable's: libnetcdf converts. Writing a `"f"` buffer into an `NC_DOUBLE` variable widens on the way in, and reading that variable into a `"f"` buffer narrows on the way out. What the buffer's format decides is how libnetcdf interprets your memory, not what ends up on disk.
25
+
26
+ ## Without an array library
27
+
28
+ `Fiddle::Pointer`, from the standard library, is a block of bytes with no format, and is read and written as the variable's own type (see [below](#format-strings)):
29
+
30
+ ```ruby
31
+ require "fiddle"
32
+
33
+ src = Fiddle::Pointer.malloc(12 * 8, Fiddle::RUBY_FREE) # 12 doubles
34
+ src[0, 12 * 8] = (1..12).map(&:to_f).pack("d*")
35
+ NetCDF.nc_put_vara(ncid, varid, [0, 0], [3, 4], src)
36
+ ```
37
+
38
+ It serves as the source of a `put`. It can be read into only from fiddle 1.1.9 on (see [what a buffer has to satisfy](#what-a-buffer-has-to-satisfy)); `#to_str(nbytes)` then copies the bytes out.
39
+
40
+ ## Format strings
41
+
42
+ A buffer describes its element type with a MemoryView `format` string. The vocabulary is the one `ruby/memory_view.h` specifies — pack-template specifiers — and this gem expects it:
43
+
44
+ | format | NetCDF type |
45
+ |--------|-------------|
46
+ | `c` / `C` | `NC_BYTE` / `NC_UBYTE`, and `NC_CHAR` |
47
+ | `s` / `S` | `NC_SHORT` / `NC_USHORT` |
48
+ | `i` / `I` | `NC_INT` / `NC_UINT` |
49
+ | `q` / `Q` | `NC_INT64` / `NC_UINT64` |
50
+ | `f` | `NC_FLOAT` |
51
+ | `d` | `NC_DOUBLE` |
52
+
53
+ `l` / `L` are read as the 32-bit integers they are in that vocabulary, and `e` / `E` / `g` / `G` as the floats that carry their own byte order (below). PEP 3118's `b` / `B` and `h` / `H` are accepted too, for producers that spell the 1- and 2-byte integers PEP's way.
54
+
55
+ A format that disagrees with the buffer's own `item_size` — numpy's 64-bit `l` arriving untranslated, say — raises `ArgumentError` rather than being misread.
56
+
57
+ A format may name a byte order. Ruby spells it as a suffix on an integer (`q<`, `s>`) and as a letter of its own on a float (`e` / `E` little-endian single / double, `g` / `G` big-endian); a suffix on a float (`d<`) is not Ruby's and is refused as unsupported. PEP 3118 spells it as a prefix on the whole string (`<d`, plus the order-neutral `=` `@` `|` `^`). All of these are read. One naming the *foreign* order raises `ArgumentError: buffer format "..." is not native-endian` — libnetcdf transfers the buffer as native memory, so byte-swapping is the producer's job.
58
+
59
+ Only a single numeric element is accepted. Repeat counts, records (`T{...}`), and mixed formats (`dd`) have no netCDF counterpart and are rejected.
60
+
61
+ A buffer with no format at all is passed through as raw bytes of the variable's own type. So is any buffer for an opaque variable, whose element size netCDF already knows and whose data has no element type for a format to name — see [opaque I/O](03_reference.md#opaque-fixed-size-raw-bytes). An enum variable moves its base type whatever the buffer holds, so a formatted buffer for one must name that base type; another raises `ArgumentError` rather than being reinterpreted. An `NC_CHAR` buffer moves bytes, so its format, when it has one, must name a 1-byte element.
62
+
63
+ ## Array libraries
64
+
65
+ An array class that exposes MemoryView natively is a buffer here with no adapter written for it and nothing copied on the way through. Pass the array where a buffer goes; the example takes CArray as one such class:
66
+
67
+ ```ruby
68
+ require "netcdf"
69
+ require "carray" # a MemoryView producer/consumer
70
+
71
+ src = CArray.double(3, 4).seq(1.0)
72
+ NetCDF.nc_put_vara(ncid, varid, [0, 0], [3, 4], src)
73
+
74
+ dst = CArray.double(3, 4)
75
+ NetCDF.nc_get_vara(ncid, varid, [0, 0], [3, 4], dst) # filled in place
76
+ ```
77
+
78
+ An array class that does not expose it has its data copied into one that does, once in each direction.
79
+
80
+ The gem's own code names no library: which of the two paths yours takes is decided by the protocol alone, and it moves to the first the moment it grows a MemoryView entry.
81
+
82
+ ## Types that are not buffers
83
+
84
+ Two of NetCDF's types do not fit into a single block of memory, and the gem hands them to you as Ruby objects instead:
85
+
86
+ - **`NC_STRING`** — each element is a separately allocated C string, so a `get` takes no buffer and returns a `String` or a nested `Array<String>`, and a `put` accepts the same. See [NC_STRING variables](03_reference.md#nc_string-variables).
87
+ - **Vlen** — a variable-length sequence per element. Type definition works; data I/O is not wired through yet.
88
+
89
+ ## The GVL
90
+
91
+ Array I/O releases the GVL around the libnetcdf call, so a large read or write does not block other Ruby threads. The buffer must not be touched from another thread while the call is in flight — the gem holds a raw pointer into it for the duration.
92
+
93
+ libnetcdf, and the HDF5 under it, are not thread-safe: two threads inside them at once corrupt process-wide state, even when they work on different files. So every `NetCDF` call — array I/O, and inquiry, definition, open and close alike — takes one process-wide lock. A thread that calls into NetCDF while another is in the middle of a read waits for it, with the GVL released; threads doing anything else keep running. Threads therefore do not speed up netCDF I/O itself, only the Ruby work around it. The lock covers one call at a time: a sequence of calls that must not interleave with another thread's — define, then write, say — still needs a lock of your own.
94
+
95
+ `NetCDF` can be called only from the main Ractor. A call from any other Ractor raises an error. For netCDF I/O that runs in parallel, use separate processes.