From 8846a52e6f57e312de62828e837cec619184f7ef Mon Sep 17 00:00:00 2001 From: Magnus Ulimoen Date: Fri, 1 Mar 2024 11:41:47 +0100 Subject: [PATCH 1/4] Revamp type descriptors Allows deriving types for user-defined structs and enums --- .github/workflows/ci.yml | 4 +- .github/workflows/release.yml | 4 + Cargo.toml | 2 + README.md | 7 +- netcdf-derive/.gitignore | 1 + netcdf-derive/Cargo.toml | 17 + netcdf-derive/src/lib.rs | 244 +++ netcdf-derive/tests/failing/enumnorepr.rs | 7 + netcdf-derive/tests/failing/enumnorepr.stderr | 10 + netcdf-derive/tests/failing/noreprc.rs | 6 + netcdf-derive/tests/failing/noreprc.stderr | 9 + netcdf-derive/tests/failing/union.rs | 7 + netcdf-derive/tests/failing/union.stderr | 10 + netcdf-derive/tests/failing/unittype.rs | 5 + netcdf-derive/tests/failing/unittype.stderr | 6 + netcdf-derive/tests/failing/unnamedfield.rs | 5 + .../tests/failing/unnamedfield.stderr | 7 + .../tests/failing/unsupportedattributes.rs | 22 + .../failing/unsupportedattributes.stderr | 17 + netcdf-derive/tests/fails.rs | 5 + netcdf-derive/tests/struct.rs | 207 +++ netcdf/Cargo.toml | 4 +- netcdf/examples/ncdump.rs | 119 +- netcdf/src/attribute.rs | 105 +- netcdf/src/dimension.rs | 49 +- netcdf/src/extent.rs | 6 +- netcdf/src/file.rs | 106 +- netcdf/src/group.rs | 96 +- netcdf/src/lib.rs | 27 +- netcdf/src/putget.rs | 354 ++++ netcdf/src/rc.rs | 2 +- netcdf/src/types.rs | 1528 +++++++++-------- netcdf/src/variable.rs | 1007 ++--------- netcdf/tests/attributes.rs | 5 +- netcdf/tests/group.rs | 5 +- netcdf/tests/lib.rs | 10 +- netcdf/tests/types.rs | 486 ++++-- 37 files changed, 2514 insertions(+), 1997 deletions(-) create mode 100644 netcdf-derive/.gitignore create mode 100644 netcdf-derive/Cargo.toml create mode 100644 netcdf-derive/src/lib.rs create mode 100644 netcdf-derive/tests/failing/enumnorepr.rs create mode 100644 netcdf-derive/tests/failing/enumnorepr.stderr create mode 100644 netcdf-derive/tests/failing/noreprc.rs create mode 100644 netcdf-derive/tests/failing/noreprc.stderr create mode 100644 netcdf-derive/tests/failing/union.rs create mode 100644 netcdf-derive/tests/failing/union.stderr create mode 100644 netcdf-derive/tests/failing/unittype.rs create mode 100644 netcdf-derive/tests/failing/unittype.stderr create mode 100644 netcdf-derive/tests/failing/unnamedfield.rs create mode 100644 netcdf-derive/tests/failing/unnamedfield.stderr create mode 100644 netcdf-derive/tests/failing/unsupportedattributes.rs create mode 100644 netcdf-derive/tests/failing/unsupportedattributes.stderr create mode 100644 netcdf-derive/tests/fails.rs create mode 100644 netcdf-derive/tests/struct.rs create mode 100644 netcdf/src/putget.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f30bcde..4fe6e15 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -131,7 +131,7 @@ jobs: uses: dtolnay/rust-toolchain@stable with: {toolchain: '${{matrix.rust}}'} - name: Build and test - run: cargo test -vv --features static --workspace + run: cargo test -vv --features static --workspace --exclude netcdf-derive addr_san: name: Address sanitizer @@ -147,4 +147,4 @@ jobs: env: RUSTFLAGS: "-Z sanitizer=address" RUSTDOCFLAGS: "-Z sanitizer=address" - run: cargo test --features netcdf-sys/static --target x86_64-unknown-linux-gnu --workspace + run: cargo test --features netcdf-sys/static --target x86_64-unknown-linux-gnu --workspace --exclude netcdf-derive diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 54cf29d..6cad42c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -7,6 +7,7 @@ on: - "netcdf-v*" - "netcdf-sys-v*" - "netcdf-src-v*" + - "netcdf-derive-v*" env: CARGO_TERM_COLOR: always @@ -32,6 +33,9 @@ jobs: - name: Publish netcdf-sys if: "${{ startsWith(github.ref_name, 'netcdf-sys-v') }}" run: cargo publish --package netcdf-sys --token "${{ secrets.CRATES_IO_TOKEN }}" + - name: Publish netcdf-derive + if: "${{ startsWith(github.ref_name, 'netcdf-derive-v') }}" + run: cargo publish --package netcdf --token "${{ secrets.CRATES_IO_TOKEN }}" - name: Publish netcdf if: "${{ startsWith(github.ref_name, 'netcdf-v') }}" run: cargo publish --package netcdf --token "${{ secrets.CRATES_IO_TOKEN }}" diff --git a/Cargo.toml b/Cargo.toml index 60f95eb..461fb9c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,7 @@ members = [ "netcdf", "netcdf-sys", "netcdf-src", + "netcdf-derive", ] default-members = ["netcdf", "netcdf-sys"] resolver = "2" @@ -11,4 +12,5 @@ resolver = "2" netcdf = { path = "netcdf", version = "0.9.2" } netcdf-sys = { path = "netcdf-sys", version = "0.6.2" } netcdf-src = { path = "netcdf-src", version = "0.3.6" } +netcdf-derive = { path = "netcdf-derive", version = "0.1.0" } hdf5-sys = { version = "0.8.0" } diff --git a/README.md b/README.md index eab43db..2792dfb 100644 --- a/README.md +++ b/README.md @@ -22,12 +22,9 @@ Supported: * Open/Append/Create modes * Reading from memory * Unlimited dimensions -* String variables -* User defined types (variable length, enum, compound, opaque) +* User defined types (enum, compound, other types requires additional work) -Not (yet) supported: - -* Nested user defined types +Not (yet) supported (PRs welcome): * Writing using memory-mapped file All variable data is read into a contiguous buffer, or into an [ndarray](https://github.com/rust-ndarray/ndarray) if the `ndarray` feature is activated. diff --git a/netcdf-derive/.gitignore b/netcdf-derive/.gitignore new file mode 100644 index 0000000..d57580f --- /dev/null +++ b/netcdf-derive/.gitignore @@ -0,0 +1 @@ +*.nc diff --git a/netcdf-derive/Cargo.toml b/netcdf-derive/Cargo.toml new file mode 100644 index 0000000..7bf5d2c --- /dev/null +++ b/netcdf-derive/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "netcdf-derive" +version = "0.1.0" +edition = "2021" + +[lib] +proc-macro = true + +[dependencies] +syn = "2.0.48" +quote = "1.0.35" +proc-macro2 = "1.0.78" +proc-macro-error = "1.0.4" + +[dev-dependencies] +netcdf = { workspace = true } +trybuild = "1.0.89" diff --git a/netcdf-derive/src/lib.rs b/netcdf-derive/src/lib.rs new file mode 100644 index 0000000..fbdf9cd --- /dev/null +++ b/netcdf-derive/src/lib.rs @@ -0,0 +1,244 @@ +use proc_macro2::{Ident, TokenStream}; +use proc_macro_error::{abort, proc_macro_error}; +use quote::quote; +use syn::{ + parse_macro_input, Data, DataEnum, DataStruct, DeriveInput, Fields, FieldsNamed, LitStr, Type, + Variant, +}; + +#[proc_macro_derive(NcType, attributes(netcdf))] +/// Derives `NcTypeDescriptor` for user defined types. +/// +/// See the documentation under `netcdf::TypeDescriptor` for examples +/// +/// Use `#[netcdf(rename = "name")]` to +/// rename field names or enum member names, or the name +/// of the compound/enum. +/// +/// Types one derives `NcType` for must have some properties to +/// ensure correctness: +/// * Structs must have `repr(C)` to ensure layout compatibility +/// * Structs must be packed (no padding allowed) +/// * Enums must have `repr(T)` where `T` is an int type (`{i/u}{8/16/32/64}`) +#[proc_macro_error] +pub fn derive(stream: proc_macro::TokenStream) -> proc_macro::TokenStream { + let input = parse_macro_input!(stream as DeriveInput); + let name = &input.ident; + let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); + + let mut renamed = None; + let mut repr_c = false; + for attr in &input.attrs { + if attr.path().is_ident("netcdf") { + attr.parse_nested_meta(|meta| { + if meta.path.is_ident("rename") { + renamed = Some(meta.value()?.parse::()?.value()); + } else { + abort!(meta.path, "NcType encountered an unknown attribute"); + } + Ok(()) + }) + .unwrap(); + } else if attr.path().is_ident("repr") { + attr.parse_nested_meta(|meta| { + if meta.path.is_ident("C") { + repr_c = true; + } + Ok(()) + }) + .unwrap(); + } + } + let ncname = renamed.unwrap_or_else(|| name.to_string()); + + let body = match input.data { + Data::Struct(DataStruct { + struct_token: _, + ref fields, + semi_token: _, + }) => { + if !repr_c { + abort!( + input, + "Can not derive NcType for struct without fixed layout"; + help = "struct must have attribute #[repr(C)]" + ); + } + match fields { + Fields::Named(fields) => impl_compound(name, &ncname, fields.clone()), + Fields::Unnamed(f) => { + abort!(f, "Can not derive NcType for struct with unnamed field"; note="#[derive(NcType)]") + } + Fields::Unit => abort!(input, "Can not derive NcType for unit struct"), + } + } + Data::Enum(DataEnum { + enum_token: _, + brace_token: _, + ref variants, + }) => { + let mut basetyp = None; + for attr in &input.attrs { + if attr.path().is_ident("repr") { + attr.parse_nested_meta(|meta| { + for item in ["u8", "u16", "u32", "u64", "i8", "i16", "i32", "i64"] { + if meta.path.is_ident(item) { + basetyp = Some(meta.path.get_ident().unwrap().clone()); + } + } + Ok(()) + }) + .unwrap(); + } + } + let Some(basetyp) = basetyp else { + abort!( + input, + "Can not derive NcType for enum without suitable repr"; + help="Add #[repr(i32)] (or another integer type) as an attribute to the enum" + ); + }; + impl_enum(/*&name,*/ &ncname, &basetyp, variants.iter()) + } + Data::Union(_) => abort!( + input, + "Can not derive NcType for union"; + note = "netCDF has no concept of Union type" + ), + }; + + let expanded = quote! { + const _: () = { + use netcdf::types::*; + + #[automatically_derived] + unsafe impl #impl_generics NcTypeDescriptor for #name #ty_generics #where_clause { + fn type_descriptor() -> NcVariableType { + #body + } + } + }; + }; + proc_macro::TokenStream::from(expanded) +} + +fn impl_compound(ty: &Ident, ncname: &str, fields: FieldsNamed) -> TokenStream { + struct FieldInfo { + name: String, + typ: Type, + } + let mut items: Vec = vec![]; + + for field in fields.named { + let ident = field.ident.expect("Field must have a name").clone(); + let mut rename = None; + for attr in field.attrs { + if attr.path().is_ident("netcdf") { + attr.parse_nested_meta(|meta| { + if meta.path.is_ident("rename") { + rename = Some(meta.value()?.parse::()?.value()); + } else { + abort!(meta.path, "NcType encountered an unknown attribute") + } + Ok(()) + }) + .unwrap(); + } + } + let name = rename.unwrap_or_else(|| ident.to_string()); + items.push(FieldInfo { + name, + typ: field.ty, + }); + } + + let fieldnames = items + .iter() + .map(|item| item.name.clone()) + .collect::>(); + let typeids = items + .iter() + .map(|item| item.typ.clone()) + .collect::>(); + let fieldinfo = quote!(vec![#( + ( + (#fieldnames).to_owned(), + <#typeids as NcTypeDescriptor>::type_descriptor(), + (<#typeids as NcTypeDescriptor>::ARRAY_ELEMENTS).as_dims().map(Vec::from), + ) + ),*]); + + quote! { + let mut fields = vec![]; + let mut offset = 0; + for (name, basetype, arraydims) in #fieldinfo { + let nelems = arraydims.as_ref().map_or(1, |x| x.iter().copied().product()); + let thissize = basetype.size() * nelems; + fields.push(CompoundTypeField { + name, + offset, + basetype, + arraydims, + }); + offset += thissize; + } + let compound = NcVariableType::Compound(CompoundType { + name: (#ncname).to_owned(), + size: offset, + fields, + }); + assert_eq!(compound.size(), std::mem::size_of::<#ty>(), "Compound must be packed"); + compound + } +} + +fn impl_enum<'a>( + // ty: &Ident, + ncname: &str, + basetyp: &Ident, + fields: impl Iterator, +) -> TokenStream { + let mut fieldnames = vec![]; + let mut fieldvalues = vec![]; + + for field in fields { + let ident = field.ident.clone(); + let mut rename = None; + for attr in &field.attrs { + if attr.path().is_ident("netcdf") { + attr.parse_nested_meta(|meta| { + if meta.path.is_ident("rename") { + rename = Some(meta.value()?.parse::()?.value()); + } else { + abort!(meta.path, "NcType encountered an unknown attribute") + } + Ok(()) + }) + .unwrap(); + } + } + let name = rename.unwrap_or_else(|| ident.to_string()); + fieldnames.push(name); + + let variant = match field.discriminant.clone() { + Some((_, x)) => quote!(#x), + None => fieldvalues + .last() + .map(|e| quote!(#e + 1)) + .unwrap_or(quote!(0)), + }; + + fieldvalues.push(variant); + } + + let fieldnames = quote!(vec![#(#fieldnames),*]); + let fieldvalues = quote!(vec![#(#fieldvalues),*]); + + quote! { + NcVariableType::Enum(EnumType { + name: (#ncname).to_owned(), + fieldnames: (#fieldnames).iter().map(|x| x.to_string()).collect(), + fieldvalues: ((#fieldvalues).into_iter().collect::>()).into(), + }) + } +} diff --git a/netcdf-derive/tests/failing/enumnorepr.rs b/netcdf-derive/tests/failing/enumnorepr.rs new file mode 100644 index 0000000..61fcec3 --- /dev/null +++ b/netcdf-derive/tests/failing/enumnorepr.rs @@ -0,0 +1,7 @@ +#[derive(netcdf_derive::NcType)] +enum NoRepr { + A, + B, +} + +fn main() {} diff --git a/netcdf-derive/tests/failing/enumnorepr.stderr b/netcdf-derive/tests/failing/enumnorepr.stderr new file mode 100644 index 0000000..71a1640 --- /dev/null +++ b/netcdf-derive/tests/failing/enumnorepr.stderr @@ -0,0 +1,10 @@ +error: Can not derive NcType for enum without suitable repr + --> tests/failing/enumnorepr.rs:2:1 + | +2 | / enum NoRepr { +3 | | A, +4 | | B, +5 | | } + | |_^ + | + = help: Add #[repr(i32)] (or another integer type) as an attribute to the enum diff --git a/netcdf-derive/tests/failing/noreprc.rs b/netcdf-derive/tests/failing/noreprc.rs new file mode 100644 index 0000000..7c96fe3 --- /dev/null +++ b/netcdf-derive/tests/failing/noreprc.rs @@ -0,0 +1,6 @@ +#[derive(netcdf_derive::NcType)] +struct NoReprC { + a: u8, +} + +fn main() {} diff --git a/netcdf-derive/tests/failing/noreprc.stderr b/netcdf-derive/tests/failing/noreprc.stderr new file mode 100644 index 0000000..984fc4f --- /dev/null +++ b/netcdf-derive/tests/failing/noreprc.stderr @@ -0,0 +1,9 @@ +error: Can not derive NcType for struct without fixed layout + --> tests/failing/noreprc.rs:2:1 + | +2 | / struct NoReprC { +3 | | a: u8, +4 | | } + | |_^ + | + = help: struct must have attribute #[repr(C)] diff --git a/netcdf-derive/tests/failing/union.rs b/netcdf-derive/tests/failing/union.rs new file mode 100644 index 0000000..f4256e5 --- /dev/null +++ b/netcdf-derive/tests/failing/union.rs @@ -0,0 +1,7 @@ +#[derive(netcdf_derive::NcType)] +union NoSupportedUnion { + A: i32, + B: u32, +} + +fn main() {} diff --git a/netcdf-derive/tests/failing/union.stderr b/netcdf-derive/tests/failing/union.stderr new file mode 100644 index 0000000..fbf798e --- /dev/null +++ b/netcdf-derive/tests/failing/union.stderr @@ -0,0 +1,10 @@ +error: Can not derive NcType for union + --> tests/failing/union.rs:2:1 + | +2 | / union NoSupportedUnion { +3 | | A: i32, +4 | | B: u32, +5 | | } + | |_^ + | + = note: netCDF has no concept of Union type diff --git a/netcdf-derive/tests/failing/unittype.rs b/netcdf-derive/tests/failing/unittype.rs new file mode 100644 index 0000000..54eacc3 --- /dev/null +++ b/netcdf-derive/tests/failing/unittype.rs @@ -0,0 +1,5 @@ +#[derive(netcdf_derive::NcType)] +#[repr(C)] +struct B; + +fn main() {} diff --git a/netcdf-derive/tests/failing/unittype.stderr b/netcdf-derive/tests/failing/unittype.stderr new file mode 100644 index 0000000..8af48a5 --- /dev/null +++ b/netcdf-derive/tests/failing/unittype.stderr @@ -0,0 +1,6 @@ +error: Can not derive NcType for unit struct + --> tests/failing/unittype.rs:2:1 + | +2 | / #[repr(C)] +3 | | struct B; + | |_________^ diff --git a/netcdf-derive/tests/failing/unnamedfield.rs b/netcdf-derive/tests/failing/unnamedfield.rs new file mode 100644 index 0000000..f548de9 --- /dev/null +++ b/netcdf-derive/tests/failing/unnamedfield.rs @@ -0,0 +1,5 @@ +#[repr(C)] +#[derive(netcdf_derive::NcType)] +struct UnnamedField(u8); + +fn main() {} diff --git a/netcdf-derive/tests/failing/unnamedfield.stderr b/netcdf-derive/tests/failing/unnamedfield.stderr new file mode 100644 index 0000000..9930120 --- /dev/null +++ b/netcdf-derive/tests/failing/unnamedfield.stderr @@ -0,0 +1,7 @@ +error: Can not derive NcType for struct with unnamed field + --> tests/failing/unnamedfield.rs:3:20 + | +3 | struct UnnamedField(u8); + | ^^^^ + | + = note: #[derive(NcType)] diff --git a/netcdf-derive/tests/failing/unsupportedattributes.rs b/netcdf-derive/tests/failing/unsupportedattributes.rs new file mode 100644 index 0000000..280ad12 --- /dev/null +++ b/netcdf-derive/tests/failing/unsupportedattributes.rs @@ -0,0 +1,22 @@ +#[repr(C)] +#[derive(netcdf_derive::NcType)] +struct UnsupportedNetcdfAttribute1 { + #[netcdf(gibberish)] + A: i32, +} + +#[repr(C)] +#[derive(netcdf_derive::NcType)] +#[netcdf(gibberish)] +struct UnsupportedNetcdfAttribute2 { + A: i32, +} + +#[repr(u8)] +#[derive(netcdf_derive::NcType)] +enum UnSupportedAttribute3 { + #[netcdf(gibberish)] + A, +} + +fn main() {} diff --git a/netcdf-derive/tests/failing/unsupportedattributes.stderr b/netcdf-derive/tests/failing/unsupportedattributes.stderr new file mode 100644 index 0000000..c1aaa23 --- /dev/null +++ b/netcdf-derive/tests/failing/unsupportedattributes.stderr @@ -0,0 +1,17 @@ +error: NcType encountered an unknown attribute + --> tests/failing/unsupportedattributes.rs:4:14 + | +4 | #[netcdf(gibberish)] + | ^^^^^^^^^ + +error: NcType encountered an unknown attribute + --> tests/failing/unsupportedattributes.rs:10:10 + | +10 | #[netcdf(gibberish)] + | ^^^^^^^^^ + +error: NcType encountered an unknown attribute + --> tests/failing/unsupportedattributes.rs:18:14 + | +18 | #[netcdf(gibberish)] + | ^^^^^^^^^ diff --git a/netcdf-derive/tests/fails.rs b/netcdf-derive/tests/fails.rs new file mode 100644 index 0000000..8218fe6 --- /dev/null +++ b/netcdf-derive/tests/fails.rs @@ -0,0 +1,5 @@ +#[test] +fn ui() { + let t = trybuild::TestCases::new(); + t.compile_fail("tests/failing/*.rs"); +} diff --git a/netcdf-derive/tests/struct.rs b/netcdf-derive/tests/struct.rs new file mode 100644 index 0000000..b6bae6a --- /dev/null +++ b/netcdf-derive/tests/struct.rs @@ -0,0 +1,207 @@ +use netcdf::types::*; +use netcdf_derive::NcType; + +#[repr(C, packed)] +#[derive(NcType)] +pub struct Bar { + arr: [u8; 4], + arr2: [[u16; 1]; 3], + arr3: [[[f32; 4]; 5]; 10], +} + +// mod other { +// use super::*; + +// #[repr(C, packed)] +// #[derive(NcType)] +// #[netcdf(opaque)] +// pub struct Opaque { +// a: i32, +// b: i64, +// c: [u8; 32], +// } + +// // #[repr(C)] +// // #[derive(NcType)] +// // pub struct WithOpaque { +// // #[netcdf(opaque = true)] +// // blob: [u8; 1024], +// // } +// } + +// use other::*; + +#[test] +fn test_impl_foo() { + #[repr(C, packed)] + #[derive(NcType)] + pub struct Foo { + a: i32, + b: i32, + c: i64, + dsdfjkj: f64, + } + + let auto_tp = Foo::type_descriptor(); + let manual_tp = NcVariableType::Compound(CompoundType { + name: "Foo".to_owned(), + size: 24, + fields: vec![ + CompoundTypeField { + name: "a".to_owned(), + basetype: i32::type_descriptor(), + arraydims: None, + offset: 0, + }, + CompoundTypeField { + name: "b".to_owned(), + basetype: i32::type_descriptor(), + arraydims: None, + offset: 4, + }, + CompoundTypeField { + name: "c".to_owned(), + basetype: i64::type_descriptor(), + arraydims: None, + offset: 8, + }, + CompoundTypeField { + name: "dsdfjkj".to_owned(), + basetype: f64::type_descriptor(), + arraydims: None, + offset: 16, + }, + ], + }); + assert_eq!(auto_tp, manual_tp); +} + +#[test] +fn test_impl_array() { + #[repr(C, packed)] + #[derive(NcType)] + pub struct Foo { + a: i32, + b: [[u8; 4]; 5], + } + + let auto_tp = Foo::type_descriptor(); + let manual_tp = NcVariableType::Compound(CompoundType { + name: "Foo".to_owned(), + size: 4 + 1 * (4 * 5), + fields: vec![ + CompoundTypeField { + name: "a".to_owned(), + basetype: i32::type_descriptor(), + arraydims: None, + offset: 0, + }, + CompoundTypeField { + name: "b".to_owned(), + basetype: u8::type_descriptor(), + arraydims: Some(vec![4, 5]), + offset: 4, + }, + ], + }); + assert_eq!(auto_tp, manual_tp); +} + +#[test] +fn test_renamed() { + #[repr(C)] + #[derive(NcType)] + #[netcdf(rename = "renamed")] + pub struct Renamed { + #[netcdf(rename = "other")] + item: u8, + } + + let manual_tp = NcVariableType::Compound(CompoundType { + name: "renamed".to_owned(), + size: 1, + fields: vec![CompoundTypeField { + name: "other".to_owned(), + basetype: u8::type_descriptor(), + arraydims: None, + offset: 0, + }], + }); + assert_eq!(Renamed::type_descriptor(), manual_tp); +} + +#[test] +fn test_impl_nested() { + #[repr(C, packed)] + #[derive(NcType)] + pub struct Foo { + a: i32, + b: [[u8; 4]; 5], + } + + #[repr(C, packed)] + #[derive(NcType)] + pub struct FooBar { + foo: Foo, + } + + let manual_tp = NcVariableType::Compound(CompoundType { + name: "FooBar".to_owned(), + size: 4 + 1 * (4 * 5), + fields: vec![CompoundTypeField { + name: "foo".to_owned(), + basetype: Foo::type_descriptor(), + arraydims: None, + offset: 0, + }], + }); + assert_eq!(FooBar::type_descriptor(), manual_tp); +} + +#[test] +fn test_impl_enum() { + #[repr(i8)] + #[derive(NcType)] + pub enum EnumOne { + A = 4, + B = 5, + C = 7, + D = 2, + E = Self::A as i8 - 50, + } + + let manual_tp = NcVariableType::Enum(EnumType { + name: "EnumOne".to_owned(), + fieldnames: vec![ + "A".to_owned(), + "B".to_owned(), + "C".to_owned(), + "D".to_owned(), + "E".to_owned(), + ], + fieldvalues: vec![ + EnumOne::A as i8, + EnumOne::B as _, + EnumOne::C as _, + EnumOne::D as _, + EnumOne::E as _, + ] + .into(), + }); + + assert_eq!(EnumOne::type_descriptor(), manual_tp); + + const VALUE: i64 = 405; + + #[repr(i64)] + #[derive(NcType)] + #[allow(unused)] + pub enum EnumTwo { + A, + B, + C, + D = 6, + F = i64::max_value(), + G = VALUE, + } +} diff --git a/netcdf/Cargo.toml b/netcdf/Cargo.toml index 4a6e857..cd63ddc 100644 --- a/netcdf/Cargo.toml +++ b/netcdf/Cargo.toml @@ -16,12 +16,14 @@ exclude = ["examples/**", "tests/**"] build = "build.rs" [features] -default = ["ndarray"] +default = ["ndarray", "derive"] static = ["netcdf-sys/static"] +derive = ["dep:netcdf-derive"] [dependencies] ndarray = { version = "0.15", optional = true } netcdf-sys = { workspace = true } +netcdf-derive = { workspace = true, optional = true } bitflags = "2.4.2" libc = "0.2.155" diff --git a/netcdf/examples/ncdump.rs b/netcdf/examples/ncdump.rs index e4d2782..9536cf0 100644 --- a/netcdf/examples/ncdump.rs +++ b/netcdf/examples/ncdump.rs @@ -1,4 +1,5 @@ use clap::Parser; +use netcdf::types::NcVariableType; #[derive(Debug, Parser)] struct Opt { @@ -38,27 +39,12 @@ fn print_file(g: &netcdf::File) -> Result<(), Box> { } } } - let mut types = g.types()?.peekable(); - if types.peek().is_some() { + let types = g.types()?.collect::>(); + if !types.is_empty() { println!("Types:"); - for t in types { - use netcdf::types::VariableType; - print!("\t{}: ", t.name()); - match t { - VariableType::Basic(_) | VariableType::String => unreachable!(), - VariableType::Opaque(o) => println!("Opaque({})", o.size()), - VariableType::Enum(_) => println!("Enum"), - VariableType::Vlen(v) => println!("Vlen({})", v.typ().name()), - VariableType::Compound(c) => { - print!("Compound({{"); - for field in c.fields() { - print!(" {}: {} ", field.name(), field.typ().name()); - } - println!("}})"); - } - } - } + print_types(&types)?; } + let mut variables = g.variables().peekable(); if variables.peek().is_some() { println!("Variables:"); @@ -68,7 +54,7 @@ fn print_file(g: &netcdf::File) -> Result<(), Box> { for d in v.dimensions() { print!(" {} ", d.name()); } - println!("): {}", v.vartype().name()); + println!("): {}", type_name(&v.vartype())); for a in v.attributes() { println!("\t\t{} = {:?}", a.name(), a.value()?); } @@ -105,38 +91,23 @@ fn print_group(g: &netcdf::Group) -> Result<(), Box> { } } } - let mut types = g.types().peekable(); - if types.peek().is_some() { + + let types = g.types().collect::>(); + if !types.is_empty() { println!("Types:"); - for t in types { - use netcdf::types::VariableType; - print!("\t{}: ", t.name()); - match t { - VariableType::Basic(_) | VariableType::String => unreachable!(), - VariableType::Opaque(o) => println!("Opaque({})", o.size()), - VariableType::Enum(_) => println!("Enum"), - VariableType::Vlen(v) => println!("Vlen({})", v.typ().name()), - VariableType::Compound(c) => { - print!("Compound({{"); - for field in c.fields() { - print!(" {}: {} ", field.name(), field.typ().name()); - } - println!("}})"); - } - } - } + print_types(&types)?; } let mut variables = g.variables().peekable(); if variables.peek().is_some() { println!("Variables:"); for v in variables { - print!("\t{}", v.name()); + print!("\t{}", type_name(&v.vartype())); print!("("); for d in v.dimensions() { print!(" {} ", d.name()); } - println!("): {}", v.vartype().name()); + println!("): {}", type_name(&v.vartype())); for a in v.attributes() { println!("\t\t{} = {:?}", a.name(), a.value()?); } @@ -156,3 +127,69 @@ fn print_group(g: &netcdf::Group) -> Result<(), Box> { Ok(()) } + +fn print_types(types: &[NcVariableType]) -> Result<(), Box> { + for t in types { + match t { + NcVariableType::Int(_) + | NcVariableType::String + | NcVariableType::Float(_) + | NcVariableType::Char => unreachable!(), + NcVariableType::Opaque(o) => { + print!("\t{}: ", o.name); + println!("Opaque({})", o.size) + } + NcVariableType::Enum(e) => { + print!("\t{}: ", e.name); + println!("Enum({})", type_name_enum(e)) + } + NcVariableType::Vlen(v) => { + print!("\t{}: ", v.name); + println!("Vlen({})", type_name(&v.basetype)) + } + NcVariableType::Compound(c) => { + print!("\t{}: ", c.name); + print!("Compound({{"); + for field in &c.fields { + print!(" {}: {} ", field.name, type_name(&field.basetype)); + } + println!("}})"); + } + } + } + Ok(()) +} +fn type_name(t: &NcVariableType) -> &str { + use netcdf::types::*; + match t { + NcVariableType::Int(IntType::U8) => "u8", + NcVariableType::Int(IntType::I8) => "i8", + NcVariableType::Int(IntType::U16) => "u16", + NcVariableType::Int(IntType::I16) => "i16", + NcVariableType::Int(IntType::U32) => "u32", + NcVariableType::Int(IntType::I32) => "i32", + NcVariableType::Int(IntType::U64) => "u64", + NcVariableType::Int(IntType::I64) => "i64", + NcVariableType::Float(FloatType::F32) => "f32", + NcVariableType::Float(FloatType::F64) => "f64", + NcVariableType::String => "string", + NcVariableType::Char => "char", + NcVariableType::Opaque(x) => &x.name, + NcVariableType::Enum(x) => &x.name, + NcVariableType::Vlen(x) => &x.name, + NcVariableType::Compound(x) => &x.name, + } +} +fn type_name_enum(t: &netcdf::types::EnumType) -> &str { + use netcdf::types::EnumTypeValues::*; + match &t.fieldvalues { + U8(_) => "u8", + I8(_) => "i8", + U16(_) => "u16", + I16(_) => "i16", + U32(_) => "u32", + I32(_) => "i32", + U64(_) => "u64", + I64(_) => "i64", + } +} diff --git a/netcdf/src/attribute.rs b/netcdf/src/attribute.rs index ffec57d..145d414 100644 --- a/netcdf/src/attribute.rs +++ b/netcdf/src/attribute.rs @@ -8,6 +8,7 @@ use std::os::raw::c_char; use netcdf_sys::*; use super::error; +use super::utils::with_lock; /// Extra properties of a variable or a group can be represented /// with attributes. Primarily added with `add_attribute` on @@ -52,7 +53,7 @@ impl<'a> Attribute<'a> { fn num_elems(&self) -> error::Result { let mut nelems = 0; unsafe { - error::checked(super::with_lock(|| { + error::checked(with_lock(|| { nc_inq_attlen( self.ncid, self.varid, @@ -67,7 +68,7 @@ impl<'a> Attribute<'a> { fn typ(&self) -> error::Result { let mut atttype = 0; unsafe { - error::checked(super::with_lock(|| { + error::checked(with_lock(|| { nc_inq_atttype( self.ncid, self.varid, @@ -93,7 +94,7 @@ impl<'a> Attribute<'a> { 1 => { let mut value = 0; unsafe { - error::checked(super::with_lock(|| { + error::checked(with_lock(|| { nc_get_att_uchar( self.ncid, self.varid, @@ -107,7 +108,7 @@ impl<'a> Attribute<'a> { len => { let mut values = vec![0_u8; len]; unsafe { - error::checked(super::with_lock(|| { + error::checked(with_lock(|| { nc_get_att_uchar( self.ncid, self.varid, @@ -123,7 +124,7 @@ impl<'a> Attribute<'a> { 1 => { let mut value = 0; unsafe { - error::checked(super::with_lock(|| { + error::checked(with_lock(|| { nc_get_att_schar( self.ncid, self.varid, @@ -137,7 +138,7 @@ impl<'a> Attribute<'a> { len => { let mut values = vec![0; len as _]; unsafe { - error::checked(super::with_lock(|| { + error::checked(with_lock(|| { nc_get_att_schar( self.ncid, self.varid, @@ -153,7 +154,7 @@ impl<'a> Attribute<'a> { 1 => { let mut value = 0; unsafe { - error::checked(super::with_lock(|| { + error::checked(with_lock(|| { nc_get_att_short( self.ncid, self.varid, @@ -167,7 +168,7 @@ impl<'a> Attribute<'a> { len => { let mut values = vec![0; len as _]; unsafe { - error::checked(super::with_lock(|| { + error::checked(with_lock(|| { nc_get_att_short( self.ncid, self.varid, @@ -183,7 +184,7 @@ impl<'a> Attribute<'a> { 1 => { let mut value = 0; unsafe { - error::checked(super::with_lock(|| { + error::checked(with_lock(|| { nc_get_att_ushort( self.ncid, self.varid, @@ -197,7 +198,7 @@ impl<'a> Attribute<'a> { len => { let mut values = vec![0; len as _]; unsafe { - error::checked(super::with_lock(|| { + error::checked(with_lock(|| { nc_get_att_ushort( self.ncid, self.varid, @@ -213,7 +214,7 @@ impl<'a> Attribute<'a> { 1 => { let mut value = 0; unsafe { - error::checked(super::with_lock(|| { + error::checked(with_lock(|| { nc_get_att_int( self.ncid, self.varid, @@ -227,7 +228,7 @@ impl<'a> Attribute<'a> { len => { let mut values = vec![0; len as _]; unsafe { - error::checked(super::with_lock(|| { + error::checked(with_lock(|| { nc_get_att_int( self.ncid, self.varid, @@ -243,7 +244,7 @@ impl<'a> Attribute<'a> { 1 => { let mut value = 0; unsafe { - error::checked(super::with_lock(|| { + error::checked(with_lock(|| { nc_get_att_uint( self.ncid, self.varid, @@ -257,7 +258,7 @@ impl<'a> Attribute<'a> { len => { let mut values = vec![0; len as _]; unsafe { - error::checked(super::with_lock(|| { + error::checked(with_lock(|| { nc_get_att_uint( self.ncid, self.varid, @@ -273,7 +274,7 @@ impl<'a> Attribute<'a> { 1 => { let mut value = 0; unsafe { - error::checked(super::with_lock(|| { + error::checked(with_lock(|| { nc_get_att_longlong( self.ncid, self.varid, @@ -287,7 +288,7 @@ impl<'a> Attribute<'a> { len => { let mut values = vec![0; len as _]; unsafe { - error::checked(super::with_lock(|| { + error::checked(with_lock(|| { nc_get_att_longlong( self.ncid, self.varid, @@ -304,7 +305,7 @@ impl<'a> Attribute<'a> { 1 => { let mut value = 0; unsafe { - error::checked(super::with_lock(|| { + error::checked(with_lock(|| { nc_get_att_ulonglong( self.ncid, self.varid, @@ -318,7 +319,7 @@ impl<'a> Attribute<'a> { len => { let mut values = vec![0; len as _]; unsafe { - error::checked(super::with_lock(|| { + error::checked(with_lock(|| { nc_get_att_ulonglong( self.ncid, self.varid, @@ -335,7 +336,7 @@ impl<'a> Attribute<'a> { 1 => { let mut value = 0.0; unsafe { - error::checked(super::with_lock(|| { + error::checked(with_lock(|| { nc_get_att_float( self.ncid, self.varid, @@ -349,7 +350,7 @@ impl<'a> Attribute<'a> { len => { let mut values = vec![0.0; len as _]; unsafe { - error::checked(super::with_lock(|| { + error::checked(with_lock(|| { nc_get_att_float( self.ncid, self.varid, @@ -365,7 +366,7 @@ impl<'a> Attribute<'a> { 1 => { let mut value = 0.0; unsafe { - error::checked(super::with_lock(|| { + error::checked(with_lock(|| { nc_get_att_double( self.ncid, self.varid, @@ -379,7 +380,7 @@ impl<'a> Attribute<'a> { len => { let mut values = vec![0.0; len as _]; unsafe { - error::checked(super::with_lock(|| { + error::checked(with_lock(|| { nc_get_att_double( self.ncid, self.varid, @@ -395,7 +396,7 @@ impl<'a> Attribute<'a> { let lentext = attlen; let mut buf: Vec = vec![0; lentext as _]; unsafe { - error::checked(super::with_lock(|| { + error::checked(with_lock(|| { nc_get_att_text( self.ncid, self.varid, @@ -413,7 +414,7 @@ impl<'a> Attribute<'a> { let mut buf: Vec<*mut c_char> = vec![std::ptr::null_mut(); attlen]; let result; unsafe { - error::checked(super::with_lock(|| { + error::checked(with_lock(|| { nc_get_att_string( self.ncid, self.varid, @@ -432,7 +433,7 @@ impl<'a> Attribute<'a> { } }) .collect(); - super::with_lock(|| nc_free_string(attlen, buf.as_mut_ptr())); + with_lock(|| nc_free_string(attlen, buf.as_mut_ptr())); } Ok(AttributeValue::Strs(result)) } @@ -454,7 +455,7 @@ impl<'a> AttributeIterator<'a> { pub(crate) fn new(ncid: nc_type, varid: Option) -> error::Result { let mut natts = 0; unsafe { - error::checked(super::with_lock(|| { + error::checked(with_lock(|| { nc_inq_varnatts(ncid, varid.unwrap_or(NC_GLOBAL), &mut natts) }))?; } @@ -477,7 +478,7 @@ impl<'a> Iterator for AttributeIterator<'a> { let mut name = [0_u8; NC_MAX_NAME as usize + 1]; unsafe { - if let Err(e) = error::checked(super::with_lock(|| { + if let Err(e) = error::checked(with_lock(|| { nc_inq_attname( self.ncid, self.varid.unwrap_or(NC_GLOBAL), @@ -543,10 +544,10 @@ impl<'a> Attribute<'a> { error::checked(unsafe { match val { - AttributeValue::Uchar(x) => super::with_lock(|| { + AttributeValue::Uchar(x) => with_lock(|| { nc_put_att_uchar(ncid, varid, cname.as_ptr().cast(), NC_UBYTE, 1, &x) }), - AttributeValue::Uchars(x) => super::with_lock(|| { + AttributeValue::Uchars(x) => with_lock(|| { nc_put_att_uchar( ncid, varid, @@ -556,10 +557,10 @@ impl<'a> Attribute<'a> { x.as_ptr(), ) }), - AttributeValue::Schar(x) => super::with_lock(|| { + AttributeValue::Schar(x) => with_lock(|| { nc_put_att_schar(ncid, varid, cname.as_ptr().cast(), NC_BYTE, 1, &x) }), - AttributeValue::Schars(x) => super::with_lock(|| { + AttributeValue::Schars(x) => with_lock(|| { nc_put_att_schar( ncid, varid, @@ -569,10 +570,10 @@ impl<'a> Attribute<'a> { x.as_ptr(), ) }), - AttributeValue::Ushort(x) => super::with_lock(|| { + AttributeValue::Ushort(x) => with_lock(|| { nc_put_att_ushort(ncid, varid, cname.as_ptr().cast(), NC_USHORT, 1, &x) }), - AttributeValue::Ushorts(x) => super::with_lock(|| { + AttributeValue::Ushorts(x) => with_lock(|| { nc_put_att_ushort( ncid, varid, @@ -582,10 +583,10 @@ impl<'a> Attribute<'a> { x.as_ptr(), ) }), - AttributeValue::Short(x) => super::with_lock(|| { + AttributeValue::Short(x) => with_lock(|| { nc_put_att_short(ncid, varid, cname.as_ptr().cast(), NC_SHORT, 1, &x) }), - AttributeValue::Shorts(x) => super::with_lock(|| { + AttributeValue::Shorts(x) => with_lock(|| { nc_put_att_short( ncid, varid, @@ -595,10 +596,10 @@ impl<'a> Attribute<'a> { x.as_ptr(), ) }), - AttributeValue::Uint(x) => super::with_lock(|| { + AttributeValue::Uint(x) => with_lock(|| { nc_put_att_uint(ncid, varid, cname.as_ptr().cast(), NC_UINT, 1, &x) }), - AttributeValue::Uints(x) => super::with_lock(|| { + AttributeValue::Uints(x) => with_lock(|| { nc_put_att_uint( ncid, varid, @@ -608,10 +609,10 @@ impl<'a> Attribute<'a> { x.as_ptr(), ) }), - AttributeValue::Int(x) => super::with_lock(|| { - nc_put_att_int(ncid, varid, cname.as_ptr().cast(), NC_INT, 1, &x) - }), - AttributeValue::Ints(x) => super::with_lock(|| { + AttributeValue::Int(x) => { + with_lock(|| nc_put_att_int(ncid, varid, cname.as_ptr().cast(), NC_INT, 1, &x)) + } + AttributeValue::Ints(x) => with_lock(|| { nc_put_att_int( ncid, varid, @@ -621,10 +622,10 @@ impl<'a> Attribute<'a> { x.as_ptr(), ) }), - AttributeValue::Ulonglong(x) => super::with_lock(|| { + AttributeValue::Ulonglong(x) => with_lock(|| { nc_put_att_ulonglong(ncid, varid, cname.as_ptr().cast(), NC_UINT64, 1, &x) }), - AttributeValue::Ulonglongs(x) => super::with_lock(|| { + AttributeValue::Ulonglongs(x) => with_lock(|| { nc_put_att_ulonglong( ncid, varid, @@ -634,10 +635,10 @@ impl<'a> Attribute<'a> { x.as_ptr(), ) }), - AttributeValue::Longlong(x) => super::with_lock(|| { + AttributeValue::Longlong(x) => with_lock(|| { nc_put_att_longlong(ncid, varid, cname.as_ptr().cast(), NC_INT64, 1, &x) }), - AttributeValue::Longlongs(x) => super::with_lock(|| { + AttributeValue::Longlongs(x) => with_lock(|| { nc_put_att_longlong( ncid, varid, @@ -647,10 +648,10 @@ impl<'a> Attribute<'a> { x.as_ptr(), ) }), - AttributeValue::Float(x) => super::with_lock(|| { + AttributeValue::Float(x) => with_lock(|| { nc_put_att_float(ncid, varid, cname.as_ptr().cast(), NC_FLOAT, 1, &x) }), - AttributeValue::Floats(x) => super::with_lock(|| { + AttributeValue::Floats(x) => with_lock(|| { nc_put_att_float( ncid, varid, @@ -660,10 +661,10 @@ impl<'a> Attribute<'a> { x.as_ptr(), ) }), - AttributeValue::Double(x) => super::with_lock(|| { + AttributeValue::Double(x) => with_lock(|| { nc_put_att_double(ncid, varid, cname.as_ptr().cast(), NC_DOUBLE, 1, &x) }), - AttributeValue::Doubles(x) => super::with_lock(|| { + AttributeValue::Doubles(x) => with_lock(|| { nc_put_att_double( ncid, varid, @@ -673,7 +674,7 @@ impl<'a> Attribute<'a> { x.as_ptr(), ) }), - AttributeValue::Str(ref x) => super::with_lock(|| { + AttributeValue::Str(ref x) => with_lock(|| { nc_put_att_text( ncid, varid, @@ -692,7 +693,7 @@ impl<'a> Attribute<'a> { let cstring_pointers: Vec<*const c_char> = cstrings.iter().map(|cs| cs.as_ptr()).collect(); - super::with_lock(|| { + with_lock(|| { nc_put_att_string( ncid, varid, @@ -728,7 +729,7 @@ impl<'a> Attribute<'a> { }; let e = unsafe { // Checking whether the variable exists by probing for its id - super::with_lock(|| { + with_lock(|| { nc_inq_attid( ncid, varid.unwrap_or(NC_GLOBAL), diff --git a/netcdf/src/dimension.rs b/netcdf/src/dimension.rs index a259661..ab3e9dd 100644 --- a/netcdf/src/dimension.rs +++ b/netcdf/src/dimension.rs @@ -6,6 +6,7 @@ use std::marker::PhantomData; use netcdf_sys::*; use super::error; +use super::utils::with_lock; /// Represents a netcdf dimension #[derive(Debug, Clone)] @@ -35,13 +36,13 @@ impl<'g> Dimension<'g> { let mut len = 0; let err = unsafe { // Must lock in case other variables adds to the dimension length - error::checked(super::with_lock(|| { + error::checked(with_lock(|| { nc_inq_dimlen(self.id.ncid, self.id.dimid, &mut len) })) }; // Should log or handle this somehow... - err.map(|_| len).unwrap_or(0) + err.map(|()| len).unwrap_or(0) } } @@ -54,7 +55,7 @@ impl<'g> Dimension<'g> { pub fn name(&self) -> String { let mut name = vec![0_u8; NC_MAX_NAME as usize + 1]; unsafe { - error::checked(super::with_lock(|| { + error::checked(with_lock(|| { nc_inq_dimname(self.id.ncid, self.id.dimid, name.as_mut_ptr().cast()) })) .unwrap(); @@ -75,7 +76,7 @@ impl<'g> Dimension<'g> { pub(crate) fn from_name_toid(loc: nc_type, name: &str) -> error::Result> { let mut dimid = 0; let cname = super::utils::short_name_to_bytes(name)?; - let e = unsafe { super::with_lock(|| nc_inq_dimid(loc, cname.as_ptr().cast(), &mut dimid)) }; + let e = unsafe { with_lock(|| nc_inq_dimid(loc, cname.as_ptr().cast(), &mut dimid)) }; if e == NC_EBADDIM { return Ok(None); } @@ -87,7 +88,7 @@ pub(crate) fn from_name_toid(loc: nc_type, name: &str) -> error::Result(loc: nc_type, name: &str) -> error::Result>> { let mut dimid = 0; let cname = super::utils::short_name_to_bytes(name)?; - let e = unsafe { super::with_lock(|| nc_inq_dimid(loc, cname.as_ptr().cast(), &mut dimid)) }; + let e = unsafe { with_lock(|| nc_inq_dimid(loc, cname.as_ptr().cast(), &mut dimid)) }; if e == NC_EBADDIM { return Ok(None); } @@ -95,19 +96,19 @@ pub(crate) fn from_name<'f>(loc: nc_type, name: &str) -> error::Result( ) -> error::Result>>> { let mut ndims = 0; unsafe { - error::checked(super::with_lock(|| { + error::checked(with_lock(|| { nc_inq_dimids(ncid, &mut ndims, std::ptr::null_mut(), <_>::from(false)) }))?; } let mut dimids = vec![0; ndims.try_into()?]; unsafe { - error::checked(super::with_lock(|| { + error::checked(with_lock(|| { nc_inq_dimids( ncid, std::ptr::null_mut(), @@ -148,13 +149,13 @@ pub(crate) fn dimensions_from_location<'g>( let unlimdims = { let mut nunlimdims = 0; unsafe { - error::checked(super::with_lock(|| { + error::checked(with_lock(|| { nc_inq_unlimdims(ncid, &mut nunlimdims, std::ptr::null_mut()) }))?; } let mut unlimdims = Vec::with_capacity(nunlimdims.try_into()?); unsafe { - error::checked(super::with_lock(|| { + error::checked(with_lock(|| { nc_inq_unlimdims(ncid, std::ptr::null_mut(), unlimdims.as_mut_ptr()) }))?; } @@ -167,7 +168,7 @@ pub(crate) fn dimensions_from_location<'g>( let mut dimlen = 0; if !unlimdims.contains(&dimid) { unsafe { - error::checked(super::with_lock(|| nc_inq_dimlen(ncid, dimid, &mut dimlen)))?; + error::checked(with_lock(|| nc_inq_dimlen(ncid, dimid, &mut dimlen)))?; } } Ok(Dimension { @@ -184,26 +185,24 @@ pub(crate) fn dimensions_from_variable<'g>( ) -> error::Result>>> { let mut ndims = 0; unsafe { - error::checked(super::with_lock(|| { - nc_inq_varndims(ncid, varid, &mut ndims) - }))?; + error::checked(with_lock(|| nc_inq_varndims(ncid, varid, &mut ndims)))?; } let mut dimids = vec![0; ndims.try_into()?]; unsafe { - error::checked(super::with_lock(|| { + error::checked(with_lock(|| { nc_inq_vardimid(ncid, varid, dimids.as_mut_ptr()) }))?; } let unlimdims = { let mut nunlimdims = 0; unsafe { - error::checked(super::with_lock(|| { + error::checked(with_lock(|| { nc_inq_unlimdims(ncid, &mut nunlimdims, std::ptr::null_mut()) }))?; } let mut unlimdims = Vec::with_capacity(nunlimdims.try_into()?); unsafe { - error::checked(super::with_lock(|| { + error::checked(with_lock(|| { nc_inq_unlimdims(ncid, std::ptr::null_mut(), unlimdims.as_mut_ptr()) }))?; } @@ -217,7 +216,7 @@ pub(crate) fn dimensions_from_variable<'g>( let mut dimlen = 0; if !unlimdims.contains(&dimid) { unsafe { - error::checked(super::with_lock(|| nc_inq_dimlen(ncid, dimid, &mut dimlen)))?; + error::checked(with_lock(|| nc_inq_dimlen(ncid, dimid, &mut dimlen)))?; } } Ok(Dimension { @@ -234,7 +233,7 @@ pub(crate) fn dimension_from_name<'f>( ) -> error::Result>> { let cname = super::utils::short_name_to_bytes(name)?; let mut dimid = 0; - let e = unsafe { super::with_lock(|| nc_inq_dimid(ncid, cname.as_ptr().cast(), &mut dimid)) }; + let e = unsafe { with_lock(|| nc_inq_dimid(ncid, cname.as_ptr().cast(), &mut dimid)) }; if e == NC_EBADDIM { return Ok(None); } @@ -242,20 +241,20 @@ pub(crate) fn dimension_from_name<'f>( let mut dimlen = 0; unsafe { - error::checked(super::with_lock(|| nc_inq_dimlen(ncid, dimid, &mut dimlen))).unwrap(); + error::checked(with_lock(|| nc_inq_dimlen(ncid, dimid, &mut dimlen))).unwrap(); } if dimlen != 0 { // Have to check if this dimension is unlimited let mut nunlim = 0; unsafe { - error::checked(super::with_lock(|| { + error::checked(with_lock(|| { nc_inq_unlimdims(ncid, &mut nunlim, std::ptr::null_mut()) }))?; } if nunlim != 0 { let mut unlimdims = Vec::with_capacity(nunlim.try_into()?); unsafe { - error::checked(super::with_lock(|| { + error::checked(with_lock(|| { nc_inq_unlimdims(ncid, std::ptr::null_mut(), unlimdims.as_mut_ptr()) }))?; } @@ -280,7 +279,7 @@ pub(crate) fn add_dimension_at<'f>( let cname = super::utils::short_name_to_bytes(name)?; let mut dimid = 0; unsafe { - error::checked(super::with_lock(|| { + error::checked(with_lock(|| { nc_def_dim(ncid, cname.as_ptr().cast(), len, &mut dimid) }))?; } diff --git a/netcdf/src/extent.rs b/netcdf/src/extent.rs index 66891a7..05100c6 100644 --- a/netcdf/src/extent.rs +++ b/netcdf/src/extent.rs @@ -453,7 +453,7 @@ macro_rules! impl_tuple { impl_tuple! { T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, } impl From<()> for Extents { - fn from(_: ()) -> Self { + fn from((): ()) -> Self { Self::Extent(vec![]) } } @@ -511,7 +511,7 @@ impl<'a> Iterator for StartCountStrideIter<'a> { |stride| Self::Item { start, count: (start..dim.len()).step_by(stride).count(), - stride: stride as isize, + stride: stride.try_into().expect("stride must be < isize::MAX"), is_an_index: false, is_growable: dim.is_unlimited(), is_upwards_limited: false, @@ -541,7 +541,7 @@ impl<'a> Iterator for StartCountStrideIter<'a> { |stride| Self::Item { start, count: (start..end).step_by(stride).count(), - stride: stride as isize, + stride: stride.try_into().expect("stride must be < isize::MAX"), is_an_index: false, is_growable: dim.is_unlimited(), is_upwards_limited: true, diff --git a/netcdf/src/file.rs b/netcdf/src/file.rs index 0443594..6c720b5 100644 --- a/netcdf/src/file.rs +++ b/netcdf/src/file.rs @@ -10,8 +10,10 @@ use super::attribute::{Attribute, AttributeValue}; use super::dimension::{self, Dimension}; use super::error; use super::group::{Group, GroupMut}; -use super::variable::{NcPutGet, Variable, VariableMut}; +use super::types::{NcTypeDescriptor, NcVariableType}; +use super::variable::{Variable, VariableMut}; use crate::group::{get_parent_ncid_and_stem, try_get_ncid, try_get_parent_ncid_and_stem}; +use crate::utils::with_lock; #[derive(Debug)] #[repr(transparent)] @@ -22,7 +24,7 @@ pub(crate) struct RawFile { impl RawFile { fn close(self) -> error::Result<()> { let Self { ncid } = self; - error::checked(super::with_lock(|| unsafe { nc_close(ncid) })) + error::checked(with_lock(|| unsafe { nc_close(ncid) })) } } @@ -30,7 +32,7 @@ impl Drop for RawFile { fn drop(&mut self) { // Can't really do much with an error here let ncid = self.ncid; - let _err = error::checked(super::with_lock(|| unsafe { nc_close(ncid) })); + let _err = error::checked(with_lock(|| unsafe { nc_close(ncid) })); } } @@ -77,7 +79,7 @@ impl RawFile { let f = get_ffi_from_path(path); let mut ncid: nc_type = 0; unsafe { - error::checked(super::with_lock(|| { + error::checked(with_lock(|| { nc_open(f.as_ptr().cast(), options.bits(), &mut ncid) }))?; } @@ -95,7 +97,7 @@ impl RawFile { let f = get_ffi_from_path(path); let mut ncid: nc_type = -1; unsafe { - error::checked(super::with_lock(|| { + error::checked(with_lock(|| { nc_create(f.as_ptr().cast(), options.bits(), &mut ncid) }))?; } @@ -111,7 +113,7 @@ impl RawFile { let cstr = std::ffi::CString::new(name.unwrap_or("/")).unwrap(); let mut ncid = 0; unsafe { - error::checked(super::with_lock(|| { + error::checked(with_lock(|| { nc_open_mem( cstr.as_ptr(), NC_NOWRITE, @@ -143,13 +145,13 @@ impl File { let name: Vec = { let mut pathlen = 0; unsafe { - error::checked(super::with_lock(|| { + error::checked(with_lock(|| { nc_inq_path(self.0.ncid, &mut pathlen, std::ptr::null_mut()) }))?; } let mut name = vec![0_u8; pathlen + 1]; unsafe { - error::checked(super::with_lock(|| { + error::checked(with_lock(|| { nc_inq_path(self.0.ncid, std::ptr::null_mut(), name.as_mut_ptr().cast()) }))?; } @@ -172,8 +174,7 @@ impl File { /// Main entrypoint for interacting with the netcdf file. pub fn root(&self) -> Option { let mut format = 0; - unsafe { error::checked(super::with_lock(|| nc_inq_format(self.ncid(), &mut format))) } - .unwrap(); + unsafe { error::checked(with_lock(|| nc_inq_format(self.ncid(), &mut format))) }.unwrap(); match format { NC_FORMAT_NETCDF4 | NC_FORMAT_NETCDF4_CLASSIC => Some(Group { @@ -247,8 +248,9 @@ impl File { pub fn groups(&self) -> error::Result> { super::group::groups_at_ncid(self.ncid()) } + /// Return all types in the root group - pub fn types(&self) -> error::Result> { + pub fn types(&self) -> error::Result> { super::types::all_at_location(self.ncid()).map(|x| x.map(Result::unwrap)) } @@ -363,10 +365,10 @@ impl FileMut { dims: &[&str], ) -> error::Result> where - T: NcPutGet, + T: NcTypeDescriptor, { let (ncid, name) = super::group::get_parent_ncid_and_stem(self.ncid(), name)?; - VariableMut::add_from_str(ncid, T::NCTYPE, name, dims) + VariableMut::add_from_str(ncid, &T::type_descriptor(), name, dims) } /// Create a variable with the specified type @@ -374,57 +376,22 @@ impl FileMut { &'f mut self, name: &str, dims: &[&str], - typ: &super::types::VariableType, + typ: &NcVariableType, ) -> error::Result> { let (ncid, name) = super::group::get_parent_ncid_and_stem(self.ncid(), name)?; - VariableMut::add_from_str(ncid, typ.id(), name, dims) + VariableMut::add_from_str(ncid, typ, name, dims) } - /// Add an opaque datatype, with `size` bytes - pub fn add_opaque_type( - &mut self, - name: &str, - size: usize, - ) -> error::Result { - let (ncid, name) = super::group::get_parent_ncid_and_stem(self.ncid(), name)?; - super::types::OpaqueType::add(ncid, name, size) - } - /// Add a variable length datatype - pub fn add_vlen_type( - &mut self, - name: &str, - ) -> error::Result { - let (ncid, name) = super::group::get_parent_ncid_and_stem(self.ncid(), name)?; - super::types::VlenType::add::(ncid, name) - } - /// Add an enum datatype - pub fn add_enum_type( - &mut self, - name: &str, - mappings: &[(&str, T)], - ) -> error::Result { - let (ncid, name) = super::group::get_parent_ncid_and_stem(self.ncid(), name)?; - super::types::EnumType::add::(ncid, name, mappings) + /// Add a type to the file + /// Usually the file is `derive`'d using `NcType` + pub fn add_type(&mut self) -> error::Result { + crate::types::add_type(self.ncid(), T::type_descriptor(), false) } - - /// Build a compound type - pub fn add_compound_type( - &mut self, - name: &str, - ) -> error::Result { - let (ncid, name) = super::group::get_parent_ncid_and_stem(self.ncid(), name)?; - super::types::CompoundType::add(ncid, name) + /// Add a type using a descriptor + pub fn add_type_from_descriptor(&mut self, typ: NcVariableType) -> error::Result { + crate::types::add_type(self.ncid(), typ, false) } - /// Adds a variable with a basic type of string - pub fn add_string_variable<'f>( - &'f mut self, - name: &str, - dims: &[&str], - ) -> error::Result> { - let (ncid, name) = super::group::get_parent_ncid_and_stem(self.ncid(), name)?; - VariableMut::add_from_str(ncid, NC_STRING, name, dims) - } /// Adds a variable from a set of unique identifiers, recursing upwards /// from the current group if necessary. pub fn add_variable_from_identifiers<'f, T>( @@ -433,10 +400,27 @@ impl FileMut { dims: &[dimension::DimensionIdentifier], ) -> error::Result> where - T: NcPutGet, + T: NcTypeDescriptor, { let (ncid, name) = super::group::get_parent_ncid_and_stem(self.ncid(), name)?; - super::variable::add_variable_from_identifiers(ncid, name, dims, T::NCTYPE) + let Some(xtype) = super::types::find_type(ncid, &T::type_descriptor())? else { + return Err("Type not found at this location".into()); + }; + super::variable::add_variable_from_identifiers(ncid, name, dims, xtype) + } + /// Adds a variable from a set of unique identifiers, recursing upwards + /// from the current group if necessary. + pub fn add_variable_from_identifiers_with_type<'f>( + &'f mut self, + name: &str, + dims: &[dimension::DimensionIdentifier], + typ: &NcVariableType, + ) -> error::Result> { + let (ncid, name) = super::group::get_parent_ncid_and_stem(self.ncid(), name)?; + let Some(xtype) = super::types::find_type(ncid, typ)? else { + return Err("Type not found at this location".into()); + }; + super::variable::add_variable_from_identifiers(ncid, name, dims, xtype) } /// Flush pending buffers to disk to minimise data loss in case of termination. @@ -445,9 +429,7 @@ impl FileMut { /// it is recommended to instead open the file in both the reader and /// writer process with the [`Options::SHARE`] flag. pub fn sync(&self) -> error::Result<()> { - error::checked(super::with_lock(|| unsafe { - netcdf_sys::nc_sync(self.ncid()) - })) + error::checked(with_lock(|| unsafe { netcdf_sys::nc_sync(self.ncid()) })) } /// Close the file diff --git a/netcdf/src/group.rs b/netcdf/src/group.rs index a821cb6..96940f6 100644 --- a/netcdf/src/group.rs +++ b/netcdf/src/group.rs @@ -8,7 +8,9 @@ use netcdf_sys::*; use super::attribute::{Attribute, AttributeValue}; use super::dimension::Dimension; use super::error; -use super::variable::{NcPutGet, Variable, VariableMut}; +use super::types::{NcTypeDescriptor, NcVariableType}; +use super::utils::with_lock; +use super::variable::{Variable, VariableMut}; /// Main component of the netcdf format. Holds all variables, /// attributes, and dimensions. A group can always see the parents items, @@ -42,7 +44,7 @@ impl<'f> Group<'f> { pub fn name(&self) -> String { let mut name = vec![0_u8; NC_MAX_NAME as usize + 1]; unsafe { - error::checked(super::with_lock(|| { + error::checked(with_lock(|| { nc_inq_grpname(self.ncid, name.as_mut_ptr().cast()) })) .unwrap(); @@ -131,7 +133,7 @@ impl<'f> Group<'f> { } /// Return all types in this group - pub fn types(&self) -> impl Iterator { + pub fn types(&self) -> impl Iterator { super::types::all_at_location(self.ncid) .map(|x| x.map(Result::unwrap)) .unwrap() @@ -169,43 +171,17 @@ impl<'f> GroupMut<'f> { self.groups().map(|g| GroupMut(g, PhantomData)) } - /// Add an opaque datatype, with `size` bytes - pub fn add_opaque_type( - &'f mut self, - name: &str, - size: usize, - ) -> error::Result { - let (ncid, name) = super::group::get_parent_ncid_and_stem(self.id(), name)?; - super::types::OpaqueType::add(ncid, name, size) - } - - /// Add a variable length datatype - pub fn add_vlen_type( - &'f mut self, - name: &str, - ) -> error::Result { - let (ncid, name) = super::group::get_parent_ncid_and_stem(self.id(), name)?; - super::types::VlenType::add::(ncid, name) + /// Add a derived type + pub fn add_type(&mut self) -> error::Result { + crate::types::add_type(self.ncid, T::type_descriptor(), false) } - /// Add an enum datatype - pub fn add_enum_type( - &'f mut self, - name: &str, - mappings: &[(&str, T)], - ) -> error::Result { - let (ncid, name) = super::group::get_parent_ncid_and_stem(self.id(), name)?; - super::types::EnumType::add::(ncid, name, mappings) + /// Add a type using a descriptor + pub fn add_type_from_descriptor(&mut self, typ: NcVariableType) -> error::Result { + crate::types::add_type(self.ncid, typ, false) } - /// Build a compound type - pub fn add_compound_type( - &mut self, - name: &str, - ) -> error::Result { - let (ncid, name) = super::group::get_parent_ncid_and_stem(self.id(), name)?; - super::types::CompoundType::add(ncid, name) - } + /// Add an opaque datatype, with `size` bytes /// Add an attribute to the group pub fn add_attribute<'a, T>(&'a mut self, name: &str, val: T) -> error::Result> @@ -251,20 +227,11 @@ impl<'f> GroupMut<'f> { dims: &[&str], ) -> error::Result> where - T: NcPutGet, + T: NcTypeDescriptor, 'f: 'g, { let (ncid, name) = super::group::get_parent_ncid_and_stem(self.id(), name)?; - VariableMut::add_from_str(ncid, T::NCTYPE, name, dims) - } - /// Adds a variable with a basic type of string - pub fn add_string_variable<'g>( - &mut self, - name: &str, - dims: &[&str], - ) -> error::Result> { - let (ncid, name) = super::group::get_parent_ncid_and_stem(self.id(), name)?; - VariableMut::add_from_str(ncid, NC_STRING, name, dims) + VariableMut::add_from_str(ncid, &T::type_descriptor(), name, dims) } /// Adds a variable from a set of unique identifiers, recursing upwards /// from the current group if necessary. @@ -274,10 +241,13 @@ impl<'f> GroupMut<'f> { dims: &[super::dimension::DimensionIdentifier], ) -> error::Result> where - T: NcPutGet, + T: NcTypeDescriptor, { let (ncid, name) = super::group::get_parent_ncid_and_stem(self.id(), name)?; - super::variable::add_variable_from_identifiers(ncid, name, dims, T::NCTYPE) + let Some(xtype) = super::types::find_type(self.ncid, &T::type_descriptor())? else { + return Err("Type not found at this location".into()); + }; + super::variable::add_variable_from_identifiers(ncid, name, dims, xtype) } /// Create a variable with the specified type @@ -285,23 +255,38 @@ impl<'f> GroupMut<'f> { &'f mut self, name: &str, dims: &[&str], - typ: &super::types::VariableType, + typ: &super::types::NcVariableType, ) -> error::Result> { let (ncid, name) = super::group::get_parent_ncid_and_stem(self.id(), name)?; - VariableMut::add_from_str(ncid, typ.id(), name, dims) + VariableMut::add_from_str(ncid, typ, name, dims) + } + /// Adds a variable from a set of unique identifiers, recursing upwards + /// from the current group if necessary. The variable type is specified + /// using a type descriptor. + pub fn add_variable_from_identifiers_with_type<'g>( + &'g mut self, + name: &str, + dims: &[super::dimension::DimensionIdentifier], + typ: &super::types::NcVariableType, + ) -> error::Result> { + let (ncid, name) = super::group::get_parent_ncid_and_stem(self.id(), name)?; + let Some(xtype) = super::types::find_type(ncid, typ)? else { + return Err("Type is not defined".into()); + }; + super::variable::add_variable_from_identifiers(ncid, name, dims, xtype) } } pub(crate) fn groups_at_ncid<'f>(ncid: nc_type) -> error::Result>> { let mut num_grps = 0; unsafe { - error::checked(super::with_lock(|| { + error::checked(with_lock(|| { nc_inq_grps(ncid, &mut num_grps, std::ptr::null_mut()) }))?; } let mut grps = vec![0; num_grps.try_into()?]; unsafe { - error::checked(super::with_lock(|| { + error::checked(with_lock(|| { nc_inq_grps(ncid, std::ptr::null_mut(), grps.as_mut_ptr()) }))?; } @@ -326,7 +311,7 @@ pub(crate) fn add_group_at_path(mut ncid: nc_type, path: &str) -> error::Result< pub(crate) fn add_group(mut ncid: nc_type, name: &str) -> error::Result { let byte_name = super::utils::short_name_to_bytes(name)?; unsafe { - error::checked(super::with_lock(|| { + error::checked(with_lock(|| { nc_def_grp(ncid, byte_name.as_ptr().cast(), &mut ncid) }))?; } @@ -335,8 +320,7 @@ pub(crate) fn add_group(mut ncid: nc_type, name: &str) -> error::Result pub(crate) fn try_get_ncid(mut ncid: nc_type, name: &str) -> error::Result> { let byte_name = super::utils::short_name_to_bytes(name)?; - let e = - unsafe { super::with_lock(|| nc_inq_grp_ncid(ncid, byte_name.as_ptr().cast(), &mut ncid)) }; + let e = unsafe { with_lock(|| nc_inq_grp_ncid(ncid, byte_name.as_ptr().cast(), &mut ncid)) }; if e == NC_ENOGRP { return Ok(None); } diff --git a/netcdf/src/lib.rs b/netcdf/src/lib.rs index 4f51e48..0397232 100644 --- a/netcdf/src/lib.rs +++ b/netcdf/src/lib.rs @@ -110,6 +110,7 @@ pub(crate) mod error; pub(crate) mod extent; pub(crate) mod file; pub(crate) mod group; +pub(crate) mod putget; #[cfg(feature = "4.9.2")] pub mod rc; pub mod types; @@ -124,7 +125,11 @@ pub use file::FileMem; pub(crate) use file::RawFile; pub use file::{File, FileMut, Options}; pub use group::{Group, GroupMut}; -pub use variable::{Endianness, NcPutGet, Variable, VariableMut}; +#[cfg(feature = "derive")] +pub use netcdf_derive::NcType; +#[doc(inline)] +pub use types::NcTypeDescriptor; +pub use variable::{Endianness, Variable, VariableMut}; /// Open a netcdf file in create mode /// @@ -182,16 +187,9 @@ pub fn open_mem<'a>(name: Option<&str>, mem: &'a [u8]) -> error::Result nc_type>(mut f: F) -> nc_type { - let _l = netcdf_sys::libnetcdf_lock.lock().unwrap(); - f() -} - pub(crate) mod utils { use super::error; - use netcdf_sys::{NC_EMAXNAME, NC_MAX_NAME}; + use netcdf_sys::{nc_type, NC_EMAXNAME, NC_MAX_NAME}; /// Use this function for short `netCDF` names to avoid the allocation /// for a `CString` pub(crate) fn short_name_to_bytes(name: &str) -> error::Result<[u8; NC_MAX_NAME as usize + 1]> { @@ -204,4 +202,15 @@ pub(crate) mod utils { Ok(bytes) } } + + /// All functions should be wrapped in this locker. Disregarding this, expect + /// segfaults, especially on non-threadsafe hdf5 builds + pub(crate) fn with_lock nc_type>(mut f: F) -> nc_type { + let _l = netcdf_sys::libnetcdf_lock.lock().unwrap(); + f() + } + + pub(crate) fn checked_with_lock nc_type>(f: F) -> error::Result<()> { + error::checked(with_lock(f)) + } } diff --git a/netcdf/src/putget.rs b/netcdf/src/putget.rs new file mode 100644 index 0000000..533dc68 --- /dev/null +++ b/netcdf/src/putget.rs @@ -0,0 +1,354 @@ +use super::types::*; +use crate::{error::Result, utils::checked_with_lock}; + +fn conversion_supported(from: &NcVariableType, to: &NcVariableType) -> bool { + match (from, to) { + ( + &NcVariableType::Int(_) | &NcVariableType::Float(_), + &NcVariableType::Int(_) | &NcVariableType::Float(_), + ) => true, + (from, to) => from == to, + } +} + +#[allow(clippy::too_many_lines)] +fn get_vars_mono( + var: &crate::Variable, + tp: &NcVariableType, + start: &[usize], + count: &[usize], + stride: &[isize], + values: *mut std::ffi::c_void, +) -> Result<()> { + let var_tp = var.vartype(); + + if !conversion_supported(&var_tp, tp) { + return Err("Conversion not supported".into()); + } + + match tp { + NcVariableType::Int(IntType::U8) => checked_with_lock(|| unsafe { + netcdf_sys::nc_get_vars_uchar( + var.ncid, + var.varid, + start.as_ptr(), + count.as_ptr(), + stride.as_ptr(), + values.cast(), + ) + }), + NcVariableType::Int(IntType::I8) => checked_with_lock(|| unsafe { + netcdf_sys::nc_get_vars_schar( + var.ncid, + var.varid, + start.as_ptr(), + count.as_ptr(), + stride.as_ptr(), + values.cast(), + ) + }), + NcVariableType::Int(IntType::U16) => checked_with_lock(|| unsafe { + netcdf_sys::nc_get_vars_ushort( + var.ncid, + var.varid, + start.as_ptr(), + count.as_ptr(), + stride.as_ptr(), + values.cast(), + ) + }), + NcVariableType::Int(IntType::I16) => checked_with_lock(|| unsafe { + netcdf_sys::nc_get_vars_short( + var.ncid, + var.varid, + start.as_ptr(), + count.as_ptr(), + stride.as_ptr(), + values.cast(), + ) + }), + NcVariableType::Int(IntType::U32) => checked_with_lock(|| unsafe { + netcdf_sys::nc_get_vars_uint( + var.ncid, + var.varid, + start.as_ptr(), + count.as_ptr(), + stride.as_ptr(), + values.cast(), + ) + }), + NcVariableType::Int(IntType::I32) => checked_with_lock(|| unsafe { + netcdf_sys::nc_get_vars_int( + var.ncid, + var.varid, + start.as_ptr(), + count.as_ptr(), + stride.as_ptr(), + values.cast(), + ) + }), + NcVariableType::Int(IntType::U64) => checked_with_lock(|| unsafe { + netcdf_sys::nc_get_vars_ulonglong( + var.ncid, + var.varid, + start.as_ptr(), + count.as_ptr(), + stride.as_ptr(), + values.cast(), + ) + }), + NcVariableType::Int(IntType::I64) => checked_with_lock(|| unsafe { + netcdf_sys::nc_get_vars_longlong( + var.ncid, + var.varid, + start.as_ptr(), + count.as_ptr(), + stride.as_ptr(), + values.cast(), + ) + }), + NcVariableType::Float(FloatType::F32) => checked_with_lock(|| unsafe { + netcdf_sys::nc_get_vars_float( + var.ncid, + var.varid, + start.as_ptr(), + count.as_ptr(), + stride.as_ptr(), + values.cast(), + ) + }), + NcVariableType::Float(FloatType::F64) => checked_with_lock(|| unsafe { + netcdf_sys::nc_get_vars_double( + var.ncid, + var.varid, + start.as_ptr(), + count.as_ptr(), + stride.as_ptr(), + values.cast(), + ) + }), + NcVariableType::String => checked_with_lock(|| unsafe { + netcdf_sys::nc_get_vars_string( + var.ncid, + var.varid, + start.as_ptr(), + count.as_ptr(), + stride.as_ptr(), + values.cast(), + ) + }), + NcVariableType::Char + | NcVariableType::Opaque(_) + | NcVariableType::Compound(_) + | NcVariableType::Vlen(_) => checked_with_lock(|| unsafe { + netcdf_sys::nc_get_vars( + var.ncid, + var.varid, + start.as_ptr(), + count.as_ptr(), + stride.as_ptr(), + values.cast(), + ) + }), + NcVariableType::Enum(_) => { + // TODO: Safety hole if reading a file where enum values are + // invalid (e.g. uninitialised, set by nc_put_vars using invalid args) + checked_with_lock(|| unsafe { + netcdf_sys::nc_get_vars( + var.ncid, + var.varid, + start.as_ptr(), + count.as_ptr(), + stride.as_ptr(), + values.cast(), + ) + }) + } + } +} + +pub(crate) fn get_vars( + var: &crate::Variable, + tp: &NcVariableType, + start: &[usize], + count: &[usize], + stride: &[isize], + values: *mut T, +) -> crate::error::Result<()> { + assert_eq!( + tp.size(), + std::mem::size_of::(), + "Size mismatch between type descriptor and type pointer" + ); + + get_vars_mono(var, tp, start, count, stride, values.cast()) +} + +#[allow(clippy::too_many_lines)] +fn put_vars_mono( + var: &mut crate::VariableMut, + tp: &NcVariableType, + start: &[usize], + count: &[usize], + stride: &[isize], + values: *const std::ffi::c_char, +) -> crate::error::Result<()> { + let var_tp = var.vartype(); + + if !conversion_supported(tp, &var_tp) { + return Err("Conversion not supported".into()); + } + + match tp { + NcVariableType::Int(IntType::U8) => checked_with_lock(|| unsafe { + netcdf_sys::nc_put_vars_uchar( + var.ncid, + var.varid, + start.as_ptr(), + count.as_ptr(), + stride.as_ptr(), + values.cast(), + ) + }), + NcVariableType::Int(IntType::I8) => checked_with_lock(|| unsafe { + netcdf_sys::nc_put_vars_schar( + var.ncid, + var.varid, + start.as_ptr(), + count.as_ptr(), + stride.as_ptr(), + values.cast(), + ) + }), + NcVariableType::Int(IntType::U16) => checked_with_lock(|| unsafe { + netcdf_sys::nc_put_vars_ushort( + var.ncid, + var.varid, + start.as_ptr(), + count.as_ptr(), + stride.as_ptr(), + values.cast(), + ) + }), + NcVariableType::Int(IntType::I16) => checked_with_lock(|| unsafe { + netcdf_sys::nc_put_vars_short( + var.ncid, + var.varid, + start.as_ptr(), + count.as_ptr(), + stride.as_ptr(), + values.cast(), + ) + }), + NcVariableType::Int(IntType::U32) => checked_with_lock(|| unsafe { + netcdf_sys::nc_put_vars_uint( + var.ncid, + var.varid, + start.as_ptr(), + count.as_ptr(), + stride.as_ptr(), + values.cast(), + ) + }), + NcVariableType::Int(IntType::I32) => checked_with_lock(|| unsafe { + netcdf_sys::nc_put_vars_int( + var.ncid, + var.varid, + start.as_ptr(), + count.as_ptr(), + stride.as_ptr(), + values.cast(), + ) + }), + NcVariableType::Int(IntType::U64) => checked_with_lock(|| unsafe { + netcdf_sys::nc_put_vars_ulonglong( + var.ncid, + var.varid, + start.as_ptr(), + count.as_ptr(), + stride.as_ptr(), + values.cast(), + ) + }), + NcVariableType::Int(IntType::I64) => checked_with_lock(|| unsafe { + netcdf_sys::nc_put_vars_longlong( + var.ncid, + var.varid, + start.as_ptr(), + count.as_ptr(), + stride.as_ptr(), + values.cast(), + ) + }), + NcVariableType::Float(FloatType::F32) => checked_with_lock(|| unsafe { + netcdf_sys::nc_put_vars_float( + var.ncid, + var.varid, + start.as_ptr(), + count.as_ptr(), + stride.as_ptr(), + values.cast(), + ) + }), + NcVariableType::Float(FloatType::F64) => checked_with_lock(|| unsafe { + netcdf_sys::nc_put_vars_double( + var.ncid, + var.varid, + start.as_ptr(), + count.as_ptr(), + stride.as_ptr(), + values.cast(), + ) + }), + NcVariableType::String => { + assert_eq!( + values.align_offset(std::mem::align_of::<*const std::ffi::c_char>()), + 0, + "Pointer is not aligned" + ); + #[allow(clippy::cast_ptr_alignment)] + let ptr = values.cast::<*const std::ffi::c_char>().cast_mut(); + + checked_with_lock(|| unsafe { + netcdf_sys::nc_put_vars_string( + var.ncid, + var.varid, + start.as_ptr(), + count.as_ptr(), + stride.as_ptr(), + ptr, + ) + }) + } + NcVariableType::Char + | NcVariableType::Opaque(_) + | NcVariableType::Compound(_) + | NcVariableType::Enum(_) + | NcVariableType::Vlen(_) => checked_with_lock(|| unsafe { + netcdf_sys::nc_put_vars( + var.ncid, + var.varid, + start.as_ptr(), + count.as_ptr(), + stride.as_ptr(), + values.cast(), + ) + }), + } +} + +pub(crate) fn put_vars( + var: &mut crate::VariableMut, + tp: &NcVariableType, + start: &[usize], + count: &[usize], + stride: &[isize], + values: *const T, +) -> crate::error::Result<()> { + assert_eq!( + tp.size(), + std::mem::size_of::(), + "Size mismatch between type descriptor and type pointer" + ); + put_vars_mono(var, tp, start, count, stride, values.cast()) +} diff --git a/netcdf/src/rc.rs b/netcdf/src/rc.rs index e0be69f..26a90ef 100644 --- a/netcdf/src/rc.rs +++ b/netcdf/src/rc.rs @@ -5,7 +5,7 @@ use std::ptr::NonNull; pub fn set(key: &str, value: &str) -> crate::error::Result<()> { let key = CString::new(key)?; let value = CString::new(value)?; - crate::error::checked(crate::with_lock(|| unsafe { + crate::error::checked(crate::utils::with_lock(|| unsafe { netcdf_sys::nc_rc_set(key.as_ptr(), value.as_ptr()) })) } diff --git a/netcdf/src/types.rs b/netcdf/src/types.rs index 277599e..d980078 100644 --- a/netcdf/src/types.rs +++ b/netcdf/src/types.rs @@ -1,816 +1,900 @@ -//! Contains functions and enums describing variable types - -use netcdf_sys::*; - -use super::error; -use crate::with_lock; +//! Types found in `netCDF` files +use netcdf_sys::{ + nc_type, NC_BYTE, NC_CHAR, NC_COMPOUND, NC_DOUBLE, NC_ENUM, NC_FLOAT, NC_INT, NC_INT64, + NC_MAX_NAME, NC_OPAQUE, NC_SHORT, NC_STRING, NC_UBYTE, NC_UINT, NC_UINT64, NC_USHORT, NC_VLEN, +}; + +use crate::{error::Result, utils::checked_with_lock}; + +/// This trait allows reading and writing basic and user defined types. +/// +/// Supports basic types ([`i8`], [`u8`], [`i16`], ..., [`f32`], [`f64`]) and user-defined types. +/// +/// Prefer deriving using [`NcType`][crate::NcType] when working with +/// user defined types. With the `derive` feature enabled for this crate one can +/// easily define types for reading and writing to and from `netCDF` files. +/// # Example (derive macro) +/// ```rust +/// # #[cfg(feature = "derive")] +/// #[repr(C)] +/// #[derive(netcdf::NcType, Debug, Copy, Clone)] +/// struct Foo { +/// a: i32, +/// b: u32, +/// } +/// # #[cfg(feature = "derive")] +/// #[repr(u32)] +/// #[derive(netcdf::NcType, Debug, Copy, Clone)] +/// enum Bar { +/// Egg = 3, +/// Milk, +/// } +/// # #[cfg(feature = "derive")] +/// #[repr(C)] +/// #[derive(netcdf::NcType, Debug, Copy, Clone)] +/// struct FooBar { +/// foo: Foo, +/// bar: Bar, +/// } +/// # #[cfg(feature = "derive")] +/// #[repr(C)] +/// #[derive(netcdf::NcType, Debug, Copy, Clone)] +/// struct Arrayed { +/// a: [[u8; 3]; 5], +/// b: i8, +/// } +/// # #[cfg(feature = "derive")] +/// #[repr(C)] +/// #[derive(netcdf::NcType, Debug, Copy, Clone)] +/// #[netcdf(rename = "myname")] +/// struct Renamed { +/// #[netcdf(rename = "orange")] +/// a: u64, +/// #[netcdf(rename = "apple")] +/// b: i64, +/// } +/// ``` +/// # Examples (advanced) +/// The following examples illustrates how to implement more advanced types. +/// They are not included in this crate since they either have difficulties +/// interacting with `Drop` (vlen, string) or they include design choices +/// such as naming (char) or type name (opaque, enum). +/// +/// ## Char type +/// Reading of an `netcdf_sys::NC_CHAR` can not be done by using `i8` or `u8` as +/// such types are not considered text. The below snippet can be used to define +/// a type which will read this type. +/// ```rust +/// # use netcdf::types::*; +/// #[repr(transparent)] +/// #[derive(Copy, Clone)] +/// struct NcChar(i8); +/// unsafe impl NcTypeDescriptor for NcChar { +/// fn type_descriptor() -> NcVariableType { +/// NcVariableType::Char +/// } +/// } +/// ``` +/// ## Opaque type +/// ```rust +/// # use netcdf::types::*; +/// #[repr(transparent)] +/// #[derive(Copy, Clone)] +/// struct Opaque([u8; 16]); +/// unsafe impl NcTypeDescriptor for Opaque { +/// fn type_descriptor() -> NcVariableType { +/// NcVariableType::Opaque(OpaqueType { +/// name: "Opaque".to_owned(), +/// size: std::mem::size_of::() +/// }) +/// } +/// } +/// ``` +/// ## Vlen type +/// This type *must* match [`netcdf_sys::nc_vlen_t`]. Be aware that reading using this +/// type means the memory is backed by `netCDF` and should be +/// freed using [`netcdf_sys::nc_free_vlen`] or [`netcdf_sys::nc_free_vlens`] +/// to avoid memory leaks. +/// ```rust +/// # use netcdf::types::*; +/// #[repr(C)] +/// struct Vlen { +/// len: usize, +/// p: *const u8, +/// } +/// unsafe impl NcTypeDescriptor for Vlen { +/// fn type_descriptor() -> NcVariableType { +/// NcVariableType::Vlen(VlenType { +/// name: "Vlen".to_owned(), +/// basetype: Box::new(NcVariableType::Int(IntType::U8)), +/// }) +/// } +/// } +/// ``` +/// ## String type +/// String types must be freed using [`netcdf_sys::nc_free_string`]. +/// ```rust +/// # use netcdf::types::*; +/// #[repr(transparent)] +/// struct NcString(*mut std::ffi::c_char); +/// unsafe impl NcTypeDescriptor for NcString { +/// fn type_descriptor() -> NcVariableType { +/// NcVariableType::String +/// } +/// } +/// ``` +/// +/// # Safety +/// Below is a list of things to keep in mind when implementing: +/// * `Drop` interaction when types are instantiated (reading) +/// * Padding bytes in the struct +/// * Alignment of members in a struct +/// * Endianness (for opaque structs) +/// * Overlapping compound members +/// * Duplicate enum/compound names +/// * Duplicate enum values +pub unsafe trait NcTypeDescriptor { + /// Description of the type + fn type_descriptor() -> NcVariableType; + #[doc(hidden)] + /// This is here to allow e.g. [u8; 4] in compounds and should + /// be considered a hack. + /// This item is ignored in non-compounds and will lead to confusing + /// error messages if used in non-compound types. + const ARRAY_ELEMENTS: ArrayElements = ArrayElements::None; +} -/// Basic numeric types -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub enum BasicType { - /// Signed 1 byte integer - Byte, - /// ISO/ASCII character +#[derive(Clone, PartialEq, Eq, Debug)] +/// A `netCDF` type +/// +/// This enum contains all variants of types allowed by `netCDF`. +pub enum NcVariableType { + /// A compound struct + Compound(CompoundType), + /// A bag of bytes + Opaque(OpaqueType), + /// An enumeration of names/values + Enum(EnumType), + /// Ragged array + Vlen(VlenType), + /// String type + String, + /// Integer type + Int(IntType), + /// Floating type + Float(FloatType), + /// Char type Char, - /// Unsigned 1 byte integer - Ubyte, - /// Signed 2 byte integer - Short, - /// Unsigned 2 byte integer - Ushort, - /// Signed 4 byte integer - Int, - /// Unsigned 4 byte integer - Uint, - /// Signed 8 byte integer - Int64, - /// Unsigned 8 byte integer - Uint64, - /// Single precision floating point number - Float, - /// Double precision floating point number - Double, } -impl BasicType { - /// Size of the type in bytes - fn size(self) -> usize { - match self { - Self::Byte | Self::Ubyte | Self::Char => 1, - Self::Short | Self::Ushort => 2, - Self::Int | Self::Uint | Self::Float => 4, - Self::Int64 | Self::Uint64 | Self::Double => 8, - } - } - /// `nc_type` of the type - pub(crate) fn id(self) -> nc_type { - use super::NcPutGet; +impl NcVariableType { + /// Size (in bytes) of the type in memory + pub fn size(&self) -> usize { match self { - Self::Byte => i8::NCTYPE, - Self::Char => NC_CHAR, - Self::Ubyte => u8::NCTYPE, - Self::Short => i16::NCTYPE, - Self::Ushort => u16::NCTYPE, - Self::Int => i32::NCTYPE, - Self::Uint => u32::NCTYPE, - Self::Int64 => i64::NCTYPE, - Self::Uint64 => u64::NCTYPE, - Self::Float => f32::NCTYPE, - Self::Double => f64::NCTYPE, + Self::Compound(x) => x.size(), + Self::Opaque(x) => x.size(), + Self::Enum(x) => x.size(), + Self::Vlen(x) => x.size(), + Self::Int(x) => x.size(), + Self::Float(x) => x.size(), + Self::String => std::mem::size_of::<*const std::ffi::c_char>(), + Self::Char => 1, } } +} - /// `rusty` name of the type - pub fn name(self) -> &'static str { - match self { - Self::Byte => "i8", - Self::Char => "char", - Self::Ubyte => "u8", - Self::Short => "i16", - Self::Ushort => "u16", - Self::Int => "i32", - Self::Uint => "u32", - Self::Int64 => "i64", - Self::Uint64 => "u64", - Self::Float => "f32", - Self::Double => "f64", - } +/// Opaque blob of bytes with a name +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct OpaqueType { + /// Name of type + pub name: String, + /// Size of type in bytes + pub size: usize, +} +impl OpaqueType { + fn size(&self) -> usize { + self.size } } +/// Integer type used in `netCDF` +#[derive(Copy, Clone, PartialEq, Eq, Debug)] #[allow(missing_docs)] -impl BasicType { - pub fn is_i8(self) -> bool { - self == Self::Byte - } - pub fn is_char(self) -> bool { - self == Self::Char - } - pub fn is_u8(self) -> bool { - self == Self::Ubyte - } - pub fn is_i16(self) -> bool { - self == Self::Short - } - pub fn is_u16(self) -> bool { - self == Self::Ushort - } - pub fn is_i32(self) -> bool { - self == Self::Int - } - pub fn is_u32(self) -> bool { - self == Self::Uint - } - pub fn is_i64(self) -> bool { - self == Self::Int64 - } - pub fn is_u64(self) -> bool { - self == Self::Uint64 - } - pub fn is_f32(self) -> bool { - self == Self::Float - } - pub fn is_f64(self) -> bool { - self == Self::Double +pub enum IntType { + U8, + U16, + U32, + U64, + I8, + I16, + I32, + I64, +} +impl IntType { + #[allow(clippy::trivially_copy_pass_by_ref)] + fn size(&self) -> usize { + match self { + Self::U8 | Self::I8 => 1, + Self::U16 | Self::I16 => 2, + Self::U32 | Self::I32 => 4, + Self::U64 | Self::I64 => 8, + } } } -#[derive(Clone, Debug)] -/// A set of bytes which with unspecified endianess -pub struct OpaqueType { - ncid: nc_type, - id: nc_type, +/// Floating type used in `netCDF` +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +#[allow(missing_docs)] +pub enum FloatType { + F32, + F64, } - -impl OpaqueType { - /// Get the name of this opaque type - pub fn name(&self) -> String { - let mut name = [0_u8; NC_MAX_NAME as usize + 1]; - error::checked(super::with_lock(|| unsafe { - nc_inq_opaque( - self.ncid, - self.id, - name.as_mut_ptr().cast(), - std::ptr::null_mut(), - ) - })) - .unwrap(); - - let pos = name.iter().position(|&x| x == 0).unwrap_or(name.len()); - String::from_utf8(name[..pos].to_vec()).unwrap() - } - /// Number of bytes this type occupies - pub fn size(&self) -> usize { - let mut numbytes = 0; - error::checked(super::with_lock(|| unsafe { - nc_inq_opaque(self.ncid, self.id, std::ptr::null_mut(), &mut numbytes) - })) - .unwrap(); - numbytes - } - pub(crate) fn add(location: nc_type, name: &str, size: usize) -> error::Result { - let name = super::utils::short_name_to_bytes(name)?; - let mut id = 0; - error::checked(super::with_lock(|| unsafe { - nc_def_opaque(location, size, name.as_ptr().cast(), &mut id) - }))?; - - Ok(Self { ncid: location, id }) +impl FloatType { + #[allow(clippy::trivially_copy_pass_by_ref)] + fn size(&self) -> usize { + match self { + Self::F32 => 4, + Self::F64 => 8, + } } } -/// Type of variable length -#[derive(Debug, Clone)] -pub struct VlenType { - ncid: nc_type, - id: nc_type, +#[derive(Clone, PartialEq, Eq, Debug)] +/// Field of a compound struct +pub struct CompoundTypeField { + /// Name of the compound field + pub name: String, + /// Type of the compound field + pub basetype: NcVariableType, + /// Dimensionality of the compound field (if any) + pub arraydims: Option>, + /// Offset of this field (in bytes) relative to the start + /// of the compound + pub offset: usize, } -impl VlenType { - /// Name of the type - pub fn name(&self) -> String { - let mut name = [0_u8; NC_MAX_NAME as usize + 1]; - error::checked(super::with_lock(|| unsafe { - nc_inq_vlen( - self.ncid, - self.id, - name.as_mut_ptr().cast(), - std::ptr::null_mut(), - std::ptr::null_mut(), - ) - })) - .unwrap(); - - let pos = name.iter().position(|&x| x == 0).unwrap_or(name.len()); - String::from_utf8(name[..pos].to_vec()).unwrap() +#[derive(Clone, Debug)] +/// Compound/record type +pub struct CompoundType { + /// Name of the compound + pub name: String, + /// Size in bytes of the compound + pub size: usize, + /// Fields of the compound + pub fields: Vec, +} +impl CompoundType { + fn size(&self) -> usize { + self.size } +} - pub(crate) fn add(location: nc_type, name: &str) -> error::Result - where - T: super::NcPutGet, - { - let name = super::utils::short_name_to_bytes(name)?; - let mut id = 0; - error::checked(super::with_lock(|| unsafe { - nc_def_vlen(location, name.as_ptr().cast(), T::NCTYPE, &mut id) - }))?; - - Ok(Self { ncid: location, id }) +impl PartialEq for CompoundType { + fn eq(&self, other: &Self) -> bool { + if self.name != other.name { + return false; + } + if self.fields.len() != other.fields.len() { + return false; + } + if self.size() != other.size() { + return false; + } + if self.fields.is_empty() { + return true; + } + if self.fields == other.fields { + return true; + } + // Check if fields are equal if ordered differently + // by checking each element against the other, + // done both ways to ensure each element has a match + if !self + .fields + .iter() + .all(|x| other.fields.iter().any(|y| x == y)) + { + return false; + } + other + .fields + .iter() + .all(|x| self.fields.iter().any(|y| x == y)) } +} - /// Internal type - pub fn typ(&self) -> BasicType { - let mut bastyp = 0; - error::checked(super::with_lock(|| unsafe { - nc_inq_vlen( - self.ncid, - self.id, - std::ptr::null_mut(), - std::ptr::null_mut(), - &mut bastyp, - ) - })) - .unwrap(); +impl Eq for CompoundType {} - match bastyp { - NC_BYTE => BasicType::Byte, - NC_UBYTE => BasicType::Ubyte, - NC_SHORT => BasicType::Short, - NC_USHORT => BasicType::Ushort, - NC_INT => BasicType::Int, - NC_UINT => BasicType::Uint, - NC_INT64 => BasicType::Int64, - NC_UINT64 => BasicType::Uint64, - NC_FLOAT => BasicType::Float, - NC_DOUBLE => BasicType::Double, - _ => panic!("Did not expect typeid {bastyp} in this context"), +#[derive(Clone, PartialEq, Eq, Debug)] +/// Inner values of the enum type +/// +/// `netCDF` only supports integer types +#[allow(missing_docs)] +pub enum EnumTypeValues { + U8(Vec), + U16(Vec), + U32(Vec), + U64(Vec), + I8(Vec), + I16(Vec), + I32(Vec), + I64(Vec), +} +impl EnumTypeValues { + /// Inner type of the enum + fn nc_type(&self) -> netcdf_sys::nc_type { + use netcdf_sys::*; + match self { + Self::U8(_) => NC_UBYTE, + Self::I8(_) => NC_BYTE, + Self::U16(_) => NC_USHORT, + Self::I16(_) => NC_SHORT, + Self::U32(_) => NC_UINT, + Self::I32(_) => NC_INT, + Self::U64(_) => NC_UINT64, + Self::I64(_) => NC_INT64, } } } +macro_rules! from_vec { + ($ty: ty, $item: expr) => { + impl From> for EnumTypeValues { + fn from(v: Vec<$ty>) -> Self { + $item(v) + } + } + }; +} +from_vec!(u8, Self::U8); +from_vec!(u16, Self::U16); +from_vec!(u32, Self::U32); +from_vec!(u64, Self::U64); +from_vec!(i8, Self::I8); +from_vec!(i16, Self::I16); +from_vec!(i32, Self::I32); +from_vec!(i64, Self::I64); -#[derive(Debug, Clone)] -/// Multiple string values stored as integer type +#[derive(Clone, Debug)] +/// Enum type pub struct EnumType { - ncid: nc_type, - id: nc_type, + /// Name of enum + pub name: String, + /// Name of enumeration fields + pub fieldnames: Vec, + /// Values of enumeration fields + pub fieldvalues: EnumTypeValues, } - impl EnumType { - pub(crate) fn add( - ncid: nc_type, - name: &str, - mappings: &[(&str, T)], - ) -> error::Result { - let name = super::utils::short_name_to_bytes(name)?; - let mut id = 0; - error::checked(super::with_lock(|| unsafe { - nc_def_enum(ncid, T::NCTYPE, name.as_ptr().cast(), &mut id) - }))?; - - for (name, val) in mappings { - let name = super::utils::short_name_to_bytes(name)?; - error::checked(super::with_lock(|| unsafe { - nc_insert_enum(ncid, id, name.as_ptr().cast(), (val as *const T).cast()) - }))?; + /// Size of enum in bytes + fn size(&self) -> usize { + match self.fieldvalues { + EnumTypeValues::U8(_) | EnumTypeValues::I8(_) => 1, + EnumTypeValues::U16(_) | EnumTypeValues::I16(_) => 2, + EnumTypeValues::U32(_) | EnumTypeValues::I32(_) => 4, + EnumTypeValues::U64(_) | EnumTypeValues::I64(_) => 8, } - - Ok(Self { ncid, id }) } +} - /// Get the base type of the enum - pub fn typ(&self) -> BasicType { - let mut typ = 0; - error::checked(super::with_lock(|| unsafe { - nc_inq_enum( - self.ncid, - self.id, - std::ptr::null_mut(), - &mut typ, - std::ptr::null_mut(), - std::ptr::null_mut(), - ) - })) - .unwrap(); - match typ { - NC_BYTE => BasicType::Byte, - NC_UBYTE => BasicType::Ubyte, - NC_SHORT => BasicType::Short, - NC_USHORT => BasicType::Ushort, - NC_INT => BasicType::Int, - NC_UINT => BasicType::Uint, - NC_INT64 => BasicType::Int64, - NC_UINT64 => BasicType::Uint64, - NC_FLOAT => BasicType::Float, - NC_DOUBLE => BasicType::Double, - _ => panic!("Did not expect typeid {typ} in this context"), +impl PartialEq for EnumType { + fn eq(&self, other: &Self) -> bool { + if self.name != other.name { + return false; } - } - - /// Get a single member from an index - /// - /// # Safety - /// Does not check type of enum - unsafe fn member_at(&self, idx: usize) -> error::Result<(String, T)> { - let mut name = [0_u8; NC_MAX_NAME as usize + 1]; - let mut t = std::mem::MaybeUninit::::uninit(); - let idx = idx.try_into()?; - super::with_lock(|| { - nc_inq_enum_member( - self.ncid, - self.id, - idx, - name.as_mut_ptr().cast(), - t.as_mut_ptr().cast(), - ) - }); - - let pos = name.iter().position(|&x| x == 0).unwrap_or(name.len()); - let name = String::from_utf8(name[..pos].to_vec()).unwrap(); - Ok((name, t.assume_init())) - } - - /// Get all members of the enum - pub fn members( - &self, - ) -> error::Result + '_> { - let mut typ = 0; - let mut nummembers = 0; - error::checked(super::with_lock(|| unsafe { - nc_inq_enum( - self.ncid, - self.id, - std::ptr::null_mut(), - &mut typ, - std::ptr::null_mut(), - &mut nummembers, - ) - })) - .unwrap(); - if typ != T::NCTYPE { - return Err(error::Error::TypeMismatch); + if self.fieldnames.len() != other.fieldnames.len() { + return false; } - - Ok((0..nummembers).map(move |idx| unsafe { self.member_at::(idx) }.unwrap())) - } - - /// Name of the type - pub fn name(&self) -> String { - let mut name = [0_u8; NC_MAX_NAME as usize + 1]; - error::checked(super::with_lock(|| unsafe { - nc_inq_enum( - self.ncid, - self.id, - name.as_mut_ptr().cast(), - std::ptr::null_mut(), - std::ptr::null_mut(), - std::ptr::null_mut(), - ) - })) - .unwrap(); - - let pos = name.iter().position(|&x| x == 0).unwrap_or(name.len()); - String::from_utf8(name[..pos].to_vec()).unwrap() - } - - /// Get the name from the enum value - pub fn name_from_value(&self, value: i64) -> Option { - let mut name = [0_u8; NC_MAX_NAME as usize + 1]; - let e = super::with_lock(|| unsafe { - nc_inq_enum_ident(self.ncid, self.id, value, name.as_mut_ptr().cast()) - }); - if e == NC_EINVAL { - return None; + if self.fieldnames.is_empty() { + return true; + } + if self.fieldnames == other.fieldnames && self.fieldvalues == other.fieldvalues { + return true; } - error::checked(e).unwrap(); - - let pos = name.iter().position(|&x| x == 0).unwrap_or(name.len()); - Some(String::from_utf8(name[..pos].to_vec()).unwrap()) - } - - /// Size in bytes of this type - fn size(&self) -> usize { - self.typ().size() + // Check for enum fields ordered differently + macro_rules! enumtype { + ($x: expr, $y: expr) => {{ + if !self.fieldnames.iter().zip($x).all(|(x, sname)| { + other + .fieldnames + .iter() + .zip($y) + .any(|(y, oname)| sname == oname && x == y) + }) { + return false; + } + other.fieldnames.iter().zip($y).all(|(y, oname)| { + self.fieldnames + .iter() + .zip($x) + .any(|(x, sname)| sname == oname && x == y) + }) + }}; + } + match (&self.fieldvalues, &other.fieldvalues) { + (EnumTypeValues::U8(x), EnumTypeValues::U8(y)) => enumtype!(x, y), + (EnumTypeValues::U16(x), EnumTypeValues::U16(y)) => enumtype!(x, y), + (EnumTypeValues::U32(x), EnumTypeValues::U32(y)) => enumtype!(x, y), + (EnumTypeValues::U64(x), EnumTypeValues::U64(y)) => enumtype!(x, y), + (EnumTypeValues::I8(x), EnumTypeValues::I8(y)) => enumtype!(x, y), + (EnumTypeValues::I16(x), EnumTypeValues::I16(y)) => enumtype!(x, y), + (EnumTypeValues::I32(x), EnumTypeValues::I32(y)) => enumtype!(x, y), + (EnumTypeValues::I64(x), EnumTypeValues::I64(y)) => enumtype!(x, y), + _ => false, + } } } -/// A type consisting of other types -#[derive(Debug, Clone)] -pub struct CompoundType { - ncid: nc_type, - id: nc_type, -} - -impl CompoundType { - pub(crate) fn add(ncid: nc_type, name: &str) -> error::Result { - let name = super::utils::short_name_to_bytes(name)?; - - Ok(CompoundBuilder { - ncid, - name, - size: 0, - comp: Vec::new(), - }) - } +impl Eq for EnumType {} - /// Size in bytes of this type +#[derive(Clone, PartialEq, Eq, Debug)] +/// Ragged array +pub struct VlenType { + /// Name of type + pub name: String, + /// Inner type of array + pub basetype: Box, +} +impl VlenType { + #[allow(clippy::unused_self)] + /// Size in bytes fn size(&self) -> usize { - let mut size = 0; - error::checked(super::with_lock(|| unsafe { - nc_inq_compound( - self.ncid, - self.id, - std::ptr::null_mut(), - &mut size, - std::ptr::null_mut(), - ) - })) - .unwrap(); - size - } - - /// Get the name of this type - pub fn name(&self) -> String { - let mut name = [0_u8; NC_MAX_NAME as usize + 1]; - error::checked(super::with_lock(|| unsafe { - nc_inq_compound( - self.ncid, - self.id, - name.as_mut_ptr().cast(), - std::ptr::null_mut(), - std::ptr::null_mut(), - ) - })) - .unwrap(); - - let pos = name.iter().position(|&x| x == 0).unwrap_or(name.len()); - String::from_utf8(name[..pos].to_vec()).unwrap() - } - - /// Get the fields of the compound - pub fn fields(&self) -> impl Iterator { - let ncid = self.ncid; - let parent_id = self.id; - - let mut nfields = 0; - error::checked(super::with_lock(|| unsafe { - nc_inq_compound_nfields(ncid, parent_id, &mut nfields) - })) - .unwrap(); - - (0..nfields).map(move |x| CompoundField { - ncid, - parent: parent_id, - id: x, - }) + std::mem::size_of::() } } -/// Subfield of a compound -pub struct CompoundField { - ncid: nc_type, - parent: nc_type, - id: usize, +macro_rules! impl_basic { + ($ty: ty, $item: expr) => { + unsafe impl NcTypeDescriptor for $ty { + fn type_descriptor() -> NcVariableType { + $item + } + } + }; } -impl CompoundField { - /// Name of the compound field - pub fn name(&self) -> String { - let mut name = [0_u8; NC_MAX_NAME as usize + 1]; - let idx = self.id.try_into().unwrap(); - error::checked(super::with_lock(|| unsafe { - nc_inq_compound_fieldname(self.ncid, self.parent, idx, name.as_mut_ptr().cast()) - })) - .unwrap(); - - let pos = name.iter().position(|&x| x == 0).unwrap_or(name.len()); - String::from_utf8(name[..pos].to_vec()).unwrap() - } - - /// type of the field - pub fn typ(&self) -> VariableType { - let mut typ = 0; - let id = self.id.try_into().unwrap(); - error::checked(super::with_lock(|| unsafe { - nc_inq_compound_fieldtype(self.ncid, self.parent, id, &mut typ) - })) - .unwrap(); - - VariableType::from_id(self.ncid, typ).unwrap() - } - - /// Offset in bytes of this field in the compound type - pub fn offset(&self) -> usize { - let mut offset = 0; - let id = self.id.try_into().unwrap(); - error::checked(super::with_lock(|| unsafe { - nc_inq_compound_field( - self.ncid, - self.parent, - id, - std::ptr::null_mut(), - &mut offset, - std::ptr::null_mut(), - std::ptr::null_mut(), - std::ptr::null_mut(), - ) - })) - .unwrap(); - - offset - } - - /// Get dimensionality of this compound field - pub fn dimensions(&self) -> Option> { - let mut num_dims = 0; - let id = self.id.try_into().unwrap(); - error::checked(super::with_lock(|| unsafe { - nc_inq_compound_fieldndims(self.ncid, self.parent, id, &mut num_dims) - })) - .unwrap(); +#[rustfmt::skip] +impl_basic!(u8, NcVariableType::Int(IntType::U8)); +#[rustfmt::skip] +impl_basic!(u16, NcVariableType::Int(IntType::U16)); +#[rustfmt::skip] +impl_basic!(u32, NcVariableType::Int(IntType::U32)); +#[rustfmt::skip] +impl_basic!(u64, NcVariableType::Int(IntType::U64)); +#[rustfmt::skip] +impl_basic!(i8, NcVariableType::Int(IntType::I8)); +#[rustfmt::skip] +impl_basic!(i16, NcVariableType::Int(IntType::I16)); +#[rustfmt::skip] +impl_basic!(i32, NcVariableType::Int(IntType::I32)); +#[rustfmt::skip] +impl_basic!(i64, NcVariableType::Int(IntType::I64)); +#[rustfmt::skip] +impl_basic!(f32, NcVariableType::Float(FloatType::F32)); +#[rustfmt::skip] +impl_basic!(f64, NcVariableType::Float(FloatType::F64)); + +#[doc(hidden)] +#[allow(missing_docs)] +#[derive(Copy, Clone, Debug)] +pub enum ArrayElements { + None, + One([usize; 1]), + Two([usize; 2]), + Three([usize; 3]), +} - if num_dims == 0 { - return None; +impl ArrayElements { + pub fn as_dims(&self) -> Option<&[usize]> { + match self { + Self::None => None, + Self::One(x) => Some(x.as_slice()), + Self::Two(x) => Some(x.as_slice()), + Self::Three(x) => Some(x.as_slice()), } - - let mut dims = vec![0; num_dims.try_into().unwrap()]; - error::checked(super::with_lock(|| unsafe { - nc_inq_compound_fielddim_sizes(self.ncid, self.parent, id, dims.as_mut_ptr()) - })) - .unwrap(); - - Some(dims.iter().map(|&x| x.try_into().unwrap()).collect()) } } -/// A builder for a compound type -#[must_use] -pub struct CompoundBuilder { - ncid: nc_type, - name: [u8; NC_MAX_NAME as usize + 1], - size: usize, - comp: Vec<( - VariableType, - [u8; NC_MAX_NAME as usize + 1], - Option>, - )>, +macro_rules! impl_arrayed { + ($typ: ty) => { + #[doc(hidden)] + unsafe impl NcTypeDescriptor for [$typ; N] { + fn type_descriptor() -> NcVariableType { + <$typ as NcTypeDescriptor>::type_descriptor() + } + const ARRAY_ELEMENTS: ArrayElements = ArrayElements::One([N]); + } + #[doc(hidden)] + unsafe impl NcTypeDescriptor for [[$typ; N]; M] { + fn type_descriptor() -> NcVariableType { + <$typ as NcTypeDescriptor>::type_descriptor() + } + const ARRAY_ELEMENTS: ArrayElements = ArrayElements::Two([N, M]); + } + #[doc(hidden)] + unsafe impl NcTypeDescriptor + for [[[$typ; N]; M]; L] + { + fn type_descriptor() -> NcVariableType { + <$typ as NcTypeDescriptor>::type_descriptor() + } + const ARRAY_ELEMENTS: ArrayElements = ArrayElements::Three([N, M, L]); + } + }; } -impl CompoundBuilder { - /// Add a type to the compound - pub fn add_type(&mut self, name: &str, var: &VariableType) -> error::Result<&mut Self> { - self.comp - .push((var.clone(), super::utils::short_name_to_bytes(name)?, None)); +impl_arrayed!(u8); +impl_arrayed!(u16); +impl_arrayed!(u32); +impl_arrayed!(u64); +impl_arrayed!(i8); +impl_arrayed!(i16); +impl_arrayed!(i32); +impl_arrayed!(i64); +impl_arrayed!(f32); +impl_arrayed!(f64); + +/// Find a type from a given descriptor +pub(crate) fn find_type(ncid: nc_type, typ: &NcVariableType) -> Result> { + match *typ { + NcVariableType::Int(IntType::U8) => return Ok(Some(NC_UBYTE)), + NcVariableType::Int(IntType::I8) => return Ok(Some(NC_BYTE)), + NcVariableType::Int(IntType::U16) => return Ok(Some(NC_USHORT)), + NcVariableType::Int(IntType::I16) => return Ok(Some(NC_SHORT)), + NcVariableType::Int(IntType::U32) => return Ok(Some(NC_UINT)), + NcVariableType::Int(IntType::I32) => return Ok(Some(NC_INT)), + NcVariableType::Int(IntType::U64) => return Ok(Some(NC_UINT64)), + NcVariableType::Int(IntType::I64) => return Ok(Some(NC_INT64)), + NcVariableType::Float(FloatType::F32) => return Ok(Some(NC_FLOAT)), + NcVariableType::Float(FloatType::F64) => return Ok(Some(NC_DOUBLE)), + NcVariableType::String => return Ok(Some(NC_STRING)), + NcVariableType::Char => return Ok(Some(NC_CHAR)), + _ => {} + } + + let name = match &typ { + NcVariableType::Compound(x) => &x.name, + NcVariableType::Opaque(x) => &x.name, + NcVariableType::Enum(x) => &x.name, + NcVariableType::Vlen(x) => &x.name, + _ => unreachable!(), + }; - self.size += var.size(); - Ok(self) + let mut typid = 0; + let name = crate::utils::short_name_to_bytes(name)?; + let e = checked_with_lock(|| unsafe { + netcdf_sys::nc_inq_typeid(ncid, name.as_ptr().cast(), &mut typid) + }); + if matches!(e, Err(crate::Error::Netcdf(netcdf_sys::NC_EBADTYPE))) { + return Ok(None); } - - /// Add a basic numeric type - pub fn add(&mut self, name: &str) -> error::Result<&mut Self> { - let var = VariableType::from_id(self.ncid, T::NCTYPE)?; - self.add_type(name, &var) + let candidate = read_type(ncid, typid)?; + if &candidate != typ { + return Err("Found type with that name, but it was not the correct type definition".into()); } + Ok(Some(typid)) +} - /// Add an array of a basic type - pub fn add_array( - &mut self, - name: &str, - dims: &[usize], - ) -> error::Result<&mut Self> { - let var = VariableType::from_id(self.ncid, T::NCTYPE)?; - self.add_array_type(name, &var, dims) - } +/// Add a type from the given descriptor +pub(crate) fn add_type(ncid: nc_type, typ: NcVariableType, recursive: bool) -> Result { + match typ { + NcVariableType::Int(_) + | NcVariableType::Float(_) + | NcVariableType::String + | NcVariableType::Char => Err("basic type can not be added".into()), + NcVariableType::Opaque(x) => { + let name = crate::utils::short_name_to_bytes(&x.name)?; + let mut id = 0; + checked_with_lock(|| unsafe { + netcdf_sys::nc_def_opaque(ncid, x.size, name.as_ptr().cast(), &mut id) + })?; + Ok(id) + } + NcVariableType::Vlen(x) => { + let mut id = 0; + let name = crate::utils::short_name_to_bytes(&x.name)?; + + let othertype = find_type(ncid, &x.basetype)?; + let othertype = if let Some(x) = othertype { + x + } else if recursive { + add_type(ncid, *(x.basetype), recursive)? + } else { + return Err("Type not found".into()); + }; + // let basetype = find_type(ncid, &*x.name)?.expect("No type found"); + // let othertyp = read_type(ncid, x.basetype)?; + + checked_with_lock(|| unsafe { + netcdf_sys::nc_def_vlen(ncid, name.as_ptr().cast(), othertype, &mut id) + })?; + Ok(id) + } + NcVariableType::Enum(EnumType { + name, + fieldnames, + fieldvalues, + }) => { + let mut id = 0; + let name = crate::utils::short_name_to_bytes(&name)?; + let basetyp = fieldvalues.nc_type(); + checked_with_lock(|| unsafe { + netcdf_sys::nc_def_enum(ncid, basetyp, name.as_ptr().cast(), &mut id) + })?; + + macro_rules! write_fieldvalues { + ($ty: ty, $x: expr) => {{ + for (name, value) in fieldnames.iter().zip(&$x) { + let name = crate::utils::short_name_to_bytes(&name)?; + checked_with_lock(|| unsafe { + netcdf_sys::nc_insert_enum( + ncid, + id, + name.as_ptr().cast(), + (value as *const $ty).cast(), + ) + })?; + } + }}; + } - /// Add a type as an array - pub fn add_array_type( - &mut self, - name: &str, - var: &VariableType, - dims: &[usize], - ) -> error::Result<&mut Self> { - self.comp.push(( - var.clone(), - super::utils::short_name_to_bytes(name)?, - Some(dims.iter().map(|&x| x.try_into().unwrap()).collect()), - )); - - self.size += var.size() * dims.iter().product::(); - Ok(self) - } + match fieldvalues { + EnumTypeValues::U8(x) => write_fieldvalues!(u8, x), + EnumTypeValues::I8(x) => write_fieldvalues!(i8, x), + EnumTypeValues::U16(x) => write_fieldvalues!(u16, x), + EnumTypeValues::I16(x) => write_fieldvalues!(i16, x), + EnumTypeValues::U32(x) => write_fieldvalues!(u32, x), + EnumTypeValues::I32(x) => write_fieldvalues!(i32, x), + EnumTypeValues::U64(x) => write_fieldvalues!(u64, x), + EnumTypeValues::I64(x) => write_fieldvalues!(i64, x), + } - /// Finalize the compound type - pub fn build(self) -> error::Result { - let mut id = 0; - error::checked(super::with_lock(|| unsafe { - nc_def_compound(self.ncid, self.size, self.name.as_ptr().cast(), &mut id) - }))?; - - let mut offset = 0; - for (typ, name, dims) in &self.comp { - match dims { - None => { - error::checked(super::with_lock(|| unsafe { - nc_insert_compound(self.ncid, id, name.as_ptr().cast(), offset, typ.id()) - }))?; - offset += typ.size(); - } - Some(dims) => { - let dimlen = dims.len().try_into().unwrap(); - error::checked(super::with_lock(|| unsafe { - nc_insert_array_compound( - self.ncid, + Ok(id) + } + NcVariableType::Compound(x) => { + let mut xtypes = Vec::with_capacity(x.fields.len()); + for f in &x.fields { + let xtype = find_type(ncid, &f.basetype)?; + let xtype = match (recursive, xtype) { + (_, Some(xtype)) => xtype, + (true, None) => add_type(ncid, f.basetype.clone(), recursive)?, + (false, None) => return Err("Could not find subtype".into()), + }; + xtypes.push(xtype); + } + let mut id = 0; + let name = crate::utils::short_name_to_bytes(&x.name)?; + checked_with_lock(|| unsafe { + netcdf_sys::nc_def_compound(ncid, x.size(), name.as_ptr().cast(), &mut id) + })?; + + // Find all subtypes, check if compatible, add if necessary + for (f, xtype) in x.fields.iter().zip(xtypes) { + let fieldname = crate::utils::short_name_to_bytes(&f.name)?; + match f.arraydims { + None => checked_with_lock(|| unsafe { + netcdf_sys::nc_insert_compound( + ncid, id, - name.as_ptr().cast(), - offset, - typ.id(), - dimlen, - dims.as_ptr(), + fieldname.as_ptr().cast(), + f.offset, + xtype, ) - }))?; - offset += typ.size() - * dims - .iter() - .map(|x: &i32| -> usize { (*x).try_into().unwrap() }) - .product::(); + })?, + Some(ref x) => { + let ndims = x.len() as _; + let dims = x.iter().map(|&x| x as _).collect::>(); + checked_with_lock(|| unsafe { + netcdf_sys::nc_insert_array_compound( + ncid, + id, + fieldname.as_ptr().cast(), + f.offset, + xtype, + ndims, + dims.as_ptr(), + ) + })? + } } } + Ok(id) } - - Ok(CompoundType { - ncid: self.ncid, - id, - }) } } -/// Description of the variable -#[derive(Debug, Clone)] -pub enum VariableType { - /// A basic numeric type - Basic(BasicType), - /// A string type - String, - /// Some bytes - Opaque(OpaqueType), - /// Variable length array - Vlen(VlenType), - /// Enum type - Enum(EnumType), - /// Compound type - Compound(CompoundType), -} - -impl VariableType { - /// Get the basic type, if this type is a simple numeric type - pub fn as_basic(&self) -> Option { - match self { - Self::Basic(x) => Some(*x), - _ => None, - } - } - - /// Size in bytes of the type - pub(crate) fn size(&self) -> usize { - match self { - Self::Basic(b) => b.size(), - Self::String => panic!("A string does not have a defined size"), - Self::Enum(e) => e.size(), - Self::Opaque(o) => o.size(), - Self::Vlen(_) => panic!("A variable length array does not have a defined size"), - Self::Compound(c) => c.size(), - } - } - - /// Id of this type - pub(crate) fn id(&self) -> nc_type { - match self { - Self::Basic(b) => b.id(), - Self::String => NC_STRING, - Self::Enum(e) => e.id, - Self::Opaque(o) => o.id, - Self::Vlen(v) => v.id, - Self::Compound(c) => c.id, - } - } - - /// Get the name of the type. The basic numeric types will - /// have `rusty` names (u8/i32/f64/string) - pub fn name(&self) -> String { - match self { - Self::Basic(b) => b.name().into(), - Self::String => "string".into(), - Self::Enum(e) => e.name(), - Self::Opaque(o) => o.name(), - Self::Vlen(v) => v.name(), - Self::Compound(c) => c.name(), +#[allow(clippy::too_many_lines)] +/// Read a type and return the descriptor belonging to the id of the type +pub(crate) fn read_type(ncid: nc_type, xtype: nc_type) -> Result { + match xtype { + NC_UBYTE => return Ok(NcVariableType::Int(IntType::U8)), + NC_BYTE => return Ok(NcVariableType::Int(IntType::I8)), + NC_USHORT => return Ok(NcVariableType::Int(IntType::U16)), + NC_SHORT => return Ok(NcVariableType::Int(IntType::I16)), + NC_UINT => return Ok(NcVariableType::Int(IntType::U32)), + NC_INT => return Ok(NcVariableType::Int(IntType::I32)), + NC_UINT64 => return Ok(NcVariableType::Int(IntType::U64)), + NC_INT64 => return Ok(NcVariableType::Int(IntType::I64)), + NC_FLOAT => return Ok(NcVariableType::Float(FloatType::F32)), + NC_DOUBLE => return Ok(NcVariableType::Float(FloatType::F64)), + NC_STRING => return Ok(NcVariableType::String), + NC_CHAR => return Ok(NcVariableType::Char), + _ => {} + } + let mut base_xtype = 0; + let mut name = [0_u8; NC_MAX_NAME as usize + 1]; + let mut size = 0; + let mut base_enum_type = 0; + let mut fieldmembers = 0; + checked_with_lock(|| unsafe { + netcdf_sys::nc_inq_user_type( + ncid, + xtype, + name.as_mut_ptr().cast(), + &mut size, + &mut base_enum_type, + &mut fieldmembers, + &mut base_xtype, + ) + })?; + let name = std::ffi::CStr::from_bytes_until_nul(name.as_ref()) + .unwrap() + .to_str() + .unwrap(); + match base_xtype { + NC_VLEN => { + let basetype = read_type(ncid, base_enum_type)?; + Ok(NcVariableType::Vlen(VlenType { + name: name.to_owned(), + basetype: Box::new(basetype), + })) } - } -} - -#[allow(missing_docs)] -impl VariableType { - pub fn is_string(&self) -> bool { - matches!(self, Self::String) - } - pub fn is_i8(&self) -> bool { - self.as_basic().map_or(false, BasicType::is_i8) - } - pub fn is_u8(&self) -> bool { - self.as_basic().map_or(false, BasicType::is_u8) - } - pub fn is_i16(&self) -> bool { - self.as_basic().map_or(false, BasicType::is_i16) - } - pub fn is_u16(&self) -> bool { - self.as_basic().map_or(false, BasicType::is_u16) - } - pub fn is_i32(&self) -> bool { - self.as_basic().map_or(false, BasicType::is_i32) - } - pub fn is_u32(&self) -> bool { - self.as_basic().map_or(false, BasicType::is_u32) - } - pub fn is_i64(&self) -> bool { - self.as_basic().map_or(false, BasicType::is_i64) - } - pub fn is_u64(&self) -> bool { - self.as_basic().map_or(false, BasicType::is_u64) - } - pub fn is_f32(&self) -> bool { - self.as_basic().map_or(false, BasicType::is_f32) - } - pub fn is_f64(&self) -> bool { - self.as_basic().map_or(false, BasicType::is_f64) - } -} - -impl VariableType { - /// Get the variable type from the id - pub(crate) fn from_id(ncid: nc_type, xtype: nc_type) -> error::Result { - match xtype { - NC_CHAR => Ok(Self::Basic(BasicType::Char)), - NC_BYTE => Ok(Self::Basic(BasicType::Byte)), - NC_UBYTE => Ok(Self::Basic(BasicType::Ubyte)), - NC_SHORT => Ok(Self::Basic(BasicType::Short)), - NC_USHORT => Ok(Self::Basic(BasicType::Ushort)), - NC_INT => Ok(Self::Basic(BasicType::Int)), - NC_UINT => Ok(Self::Basic(BasicType::Uint)), - NC_INT64 => Ok(Self::Basic(BasicType::Int64)), - NC_UINT64 => Ok(Self::Basic(BasicType::Uint64)), - NC_FLOAT => Ok(Self::Basic(BasicType::Float)), - NC_DOUBLE => Ok(Self::Basic(BasicType::Double)), - NC_STRING => Ok(Self::String), - xtype => { - let mut base_xtype = 0; - error::checked(super::with_lock(|| unsafe { - nc_inq_user_type( + NC_OPAQUE => Ok(NcVariableType::Opaque(OpaqueType { + name: name.to_owned(), + size, + })), + NC_ENUM => { + let mut fieldnames = vec![]; + for idx in 0..fieldmembers { + let mut cname = [0_u8; NC_MAX_NAME as usize + 1]; + let idx = idx.try_into().unwrap(); + checked_with_lock(|| unsafe { + netcdf_sys::nc_inq_enum_member( ncid, xtype, + idx, + cname.as_mut_ptr().cast(), std::ptr::null_mut(), + ) + })?; + let cstr = std::ffi::CStr::from_bytes_until_nul(cname.as_slice()).unwrap(); + fieldnames.push(cstr.to_str().unwrap().to_owned()); + } + macro_rules! read_fieldvalues { + ($ty: ty) => {{ + let mut values = vec![0 as $ty; fieldmembers]; + for (idx, value) in values.iter_mut().enumerate() { + checked_with_lock(|| unsafe { + netcdf_sys::nc_inq_enum_member( + ncid, + xtype, + idx as _, + std::ptr::null_mut(), + (value as *mut $ty).cast(), + ) + })?; + } + values.into() + }}; + } + let fieldvalues = match base_enum_type { + NC_BYTE => { + read_fieldvalues!(i8) + } + NC_UBYTE => { + read_fieldvalues!(u8) + } + NC_SHORT => { + read_fieldvalues!(i16) + } + NC_USHORT => { + read_fieldvalues!(u16) + } + NC_INT => { + read_fieldvalues!(i32) + } + NC_UINT => { + read_fieldvalues!(u32) + } + NC_INT64 => { + read_fieldvalues!(i64) + } + NC_UINT64 => { + read_fieldvalues!(u64) + } + _ => unreachable!("netCDF does not support {base_enum_type} as type in enum"), + }; + Ok(NcVariableType::Enum(EnumType { + name: name.to_owned(), + fieldnames, + fieldvalues, + })) + } + NC_COMPOUND => { + let mut fields = vec![]; + for fieldid in 0..fieldmembers { + let mut fieldname = [0; NC_MAX_NAME as usize + 1]; + let mut fieldtype = 0; + let mut ndims = 0; + let mut arraydims = None; + let mut offset = 0; + let fieldid = fieldid.try_into().unwrap(); + checked_with_lock(|| unsafe { + netcdf_sys::nc_inq_compound_field( + ncid, + xtype, + fieldid, + fieldname.as_mut_ptr().cast(), + &mut offset, + &mut fieldtype, + &mut ndims, std::ptr::null_mut(), - std::ptr::null_mut(), - std::ptr::null_mut(), - &mut base_xtype, ) - }))?; - match base_xtype { - NC_VLEN => Ok(VlenType { ncid, id: xtype }.into()), - NC_OPAQUE => Ok(OpaqueType { ncid, id: xtype }.into()), - NC_ENUM => Ok(EnumType { ncid, id: xtype }.into()), - NC_COMPOUND => Ok(CompoundType { ncid, id: xtype }.into()), - _ => panic!("Unexpected base type: {base_xtype}"), + })?; + if ndims != 0 { + let mut dimsizes = vec![0; ndims.try_into().unwrap()]; + checked_with_lock(|| unsafe { + netcdf_sys::nc_inq_compound_fielddim_sizes( + ncid, + xtype, + fieldid, + dimsizes.as_mut_ptr(), + ) + })?; + arraydims = Some(dimsizes.iter().map(|&x| x.try_into().unwrap()).collect()); } + let fieldname = std::ffi::CStr::from_bytes_until_nul(fieldname.as_slice()).unwrap(); + + fields.push(CompoundTypeField { + name: fieldname.to_str().unwrap().to_owned(), + basetype: read_type(ncid, fieldtype)?, + arraydims, + offset, + }); } + Ok(NcVariableType::Compound(CompoundType { + name: name.to_owned(), + size, + fields, + })) } + _ => panic!("Unexcepted base type {base_xtype}"), } } +/// Find all user-defined types at the location pub(crate) fn all_at_location( ncid: nc_type, -) -> error::Result>> { +) -> Result>> { let typeids = { let mut num_typeids = 0; - error::checked(with_lock(|| unsafe { - nc_inq_typeids(ncid, &mut num_typeids, std::ptr::null_mut()) - }))?; + checked_with_lock(|| unsafe { + netcdf_sys::nc_inq_typeids(ncid, &mut num_typeids, std::ptr::null_mut()) + })?; let mut typeids = vec![0; num_typeids.try_into()?]; - error::checked(with_lock(|| unsafe { - nc_inq_typeids(ncid, std::ptr::null_mut(), typeids.as_mut_ptr()) - }))?; + checked_with_lock(|| unsafe { + netcdf_sys::nc_inq_typeids(ncid, std::ptr::null_mut(), typeids.as_mut_ptr()) + })?; typeids }; - Ok(typeids - .into_iter() - .map(move |x| VariableType::from_id(ncid, x))) -} - -impl From for VariableType { - fn from(v: CompoundType) -> Self { - Self::Compound(v) - } -} - -impl From for VariableType { - fn from(v: BasicType) -> Self { - Self::Basic(v) - } -} - -impl From for VariableType { - fn from(v: EnumType) -> Self { - Self::Enum(v) - } -} - -impl From for VariableType { - fn from(v: VlenType) -> Self { - Self::Vlen(v) - } + Ok(typeids.into_iter().map(move |x| read_type(ncid, x))) } -impl From for VariableType { - fn from(v: OpaqueType) -> Self { - Self::Opaque(v) +#[repr(transparent)] +/// `NC_STRING` compatible struct, no drop implementation, use with caution +pub(crate) struct NcString(pub(crate) *mut std::ffi::c_char); +unsafe impl NcTypeDescriptor for NcString { + fn type_descriptor() -> NcVariableType { + NcVariableType::String } } diff --git a/netcdf/src/variable.rs b/netcdf/src/variable.rs index 9103780..0f38beb 100644 --- a/netcdf/src/variable.rs +++ b/netcdf/src/variable.rs @@ -1,9 +1,7 @@ //! Variables in the netcdf file #![allow(clippy::similar_names)] -use std::ffi::{c_char, CStr}; + use std::marker::PhantomData; -use std::mem::MaybeUninit; -use std::ptr::addr_of; #[cfg(feature = "ndarray")] use ndarray::ArrayD; @@ -13,7 +11,8 @@ use super::attribute::{Attribute, AttributeValue}; use super::dimension::Dimension; use super::error; use super::extent::Extents; -use super::types::VariableType; +use crate::types::{NcTypeDescriptor, NcVariableType}; +use crate::utils; #[allow(clippy::doc_markdown)] /// This struct defines a `netCDF` variable. @@ -77,7 +76,7 @@ impl<'g> Variable<'g> { let cname = super::utils::short_name_to_bytes(name)?; let mut varid = 0; let e = - unsafe { super::with_lock(|| nc_inq_varid(ncid, cname.as_ptr().cast(), &mut varid)) }; + unsafe { utils::with_lock(|| nc_inq_varid(ncid, cname.as_ptr().cast(), &mut varid)) }; if e == NC_ENOTVAR { return Ok(None); } @@ -86,7 +85,7 @@ impl<'g> Variable<'g> { let mut xtype = 0; let mut ndims = 0; unsafe { - error::checked(super::with_lock(|| { + error::checked(utils::with_lock(|| { nc_inq_var( ncid, varid, @@ -100,7 +99,7 @@ impl<'g> Variable<'g> { } let mut dimids = vec![0; ndims.try_into()?]; unsafe { - error::checked(super::with_lock(|| { + error::checked(utils::with_lock(|| { nc_inq_vardimid(ncid, varid, dimids.as_mut_ptr()) }))?; } @@ -120,7 +119,7 @@ impl<'g> Variable<'g> { pub fn name(&self) -> String { let mut name = vec![0_u8; NC_MAX_NAME as usize + 1]; unsafe { - error::checked(super::with_lock(|| { + error::checked(utils::with_lock(|| { nc_inq_varname(self.ncid, self.varid, name.as_mut_ptr().cast()) })) .unwrap(); @@ -163,8 +162,8 @@ impl<'g> Variable<'g> { &self.dimensions } /// Get the type of this variable - pub fn vartype(&self) -> VariableType { - VariableType::from_id(self.ncid, self.vartype).unwrap() + pub fn vartype(&self) -> NcVariableType { + crate::types::read_type(self.ncid, self.vartype).expect("Unknown type encountered") } /// Get current length of the variable pub fn len(&self) -> usize { @@ -181,7 +180,7 @@ impl<'g> Variable<'g> { pub fn endianness(&self) -> error::Result { let mut e: nc_type = 0; unsafe { - error::checked(super::with_lock(|| { + error::checked(utils::with_lock(|| { nc_inq_var_endian(self.ncid, self.varid, &mut e) }))?; } @@ -208,7 +207,7 @@ impl<'g> VariableMut<'g> { /// Not a `netcdf-4` file or `deflate_level` not valid pub fn set_compression(&mut self, deflate_level: nc_type, shuffle: bool) -> error::Result<()> { unsafe { - error::checked(super::with_lock(|| { + error::checked(utils::with_lock(|| { nc_def_var_deflate( self.ncid, self.varid, @@ -248,7 +247,7 @@ impl<'g> VariableMut<'g> { return Err(error::Error::Overflow); } unsafe { - error::checked(super::with_lock(|| { + error::checked(utils::with_lock(|| { nc_def_var_chunking(self.ncid, self.varid, NC_CHUNKED, chunksize.as_ptr()) }))?; } @@ -257,478 +256,6 @@ impl<'g> VariableMut<'g> { } } -mod sealed { - pub trait Sealed {} -} - -#[allow(clippy::doc_markdown)] -/// This trait allow an implicit cast when fetching -/// a netCDF variable. These methods are not be called -/// directly, but used through methods on `Variable` -/// -/// # Safety -/// This trait maps directly to netCDF semantics and needs -/// to upheld invariants therein. -/// This trait is sealed and can not be implemented for -/// types outside this crate -pub trait NcPutGet: sealed::Sealed -where - Self: Sized, -{ - /// Constant corresponding to a netcdf type - const NCTYPE: nc_type; - - /// Returns a single indexed value of the variable as Self - /// - /// # Safety - /// - /// Requires `indices` to be of a valid length - unsafe fn get_var1(variable: &Variable, start: &[usize]) -> error::Result; - - #[allow(clippy::doc_markdown)] - /// Put a single value into a netCDF variable - /// - /// # Safety - /// - /// Requires `indices` to be of a valid length - unsafe fn put_var1( - variable: &mut VariableMut, - start: &[usize], - value: Self, - ) -> error::Result<()>; - - /// Get multiple values at once, without checking the validity of - /// `indices` or `slice_len` - /// - /// # Safety - /// - /// Requires `values` to be of at least size `slice_len.product()`, - /// `indices` and `slice_len` to be of a valid length - unsafe fn get_vara( - variable: &Variable, - start: &[usize], - count: &[usize], - values: *mut Self, - ) -> error::Result<()>; - - #[allow(clippy::doc_markdown)] - /// put a SLICE of values into a netCDF variable at the given index - /// - /// # Safety - /// - /// Requires `indices` and `slice_len` to be of a valid length - unsafe fn put_vara( - variable: &mut VariableMut, - start: &[usize], - count: &[usize], - values: &[Self], - ) -> error::Result<()>; - - /// get a SLICE of values into the variable, with the source - /// strided by `stride` - /// - /// # Safety - /// - /// `values` must contain space for all the data, - /// `indices`, `slice_len`, and `stride` must be of - /// at least dimension length size. - unsafe fn get_vars( - variable: &Variable, - start: &[usize], - count: &[usize], - stride: &[isize], - values: *mut Self, - ) -> error::Result<()>; - - /// put a SLICE of values into the variable, with the destination - /// strided by `stride` - /// - /// # Safety - /// - /// `values` must contain space for all the data, - /// `indices`, `slice_len`, and `stride` must be of - /// at least dimension length size. - unsafe fn put_vars( - variable: &mut VariableMut, - start: &[usize], - count: &[usize], - stride: &[isize], - values: *const Self, - ) -> error::Result<()>; - - /// get a SLICE of values into the variable, with the source - /// strided by `stride`, mapped by `map` - /// - /// # Safety - /// - /// `values` must contain space for all the data, - /// `indices`, `slice_len`, and `stride` must be of - /// at least dimension length size. - unsafe fn get_varm( - variable: &Variable, - start: &[usize], - count: &[usize], - stride: &[isize], - map: &[isize], - values: *mut Self, - ) -> error::Result<()>; - - /// put a SLICE of values into the variable, with the destination - /// strided by `stride` - /// - /// # Safety - /// - /// `values` must contain space for all the data, - /// `indices`, `slice_len`, and `stride` must be of - /// at least dimension length size. - unsafe fn put_varm( - variable: &mut VariableMut, - start: &[usize], - count: &[usize], - stride: &[isize], - map: &[isize], - values: *const Self, - ) -> error::Result<()>; -} - -#[allow(clippy::doc_markdown)] -/// This macro implements the trait NcPutGet for the type `sized_type`. -/// -/// The use of this macro reduce code duplication for the implementation of NcPutGet -/// for the common numeric types (i32, f32 ...): they only differs by the name of the -/// C function used to fetch values from the NetCDF variable (eg: `nc_get_var_ushort`, ...). -macro_rules! impl_numeric { - ( - $sized_type: ty, - $nc_type: ident, - $nc_get_var: ident, - $nc_get_vara_type: ident, - $nc_get_var1_type: ident, - $nc_put_var1_type: ident, - $nc_put_vara_type: ident, - $nc_get_vars_type: ident, - $nc_put_vars_type: ident, - $nc_get_varm_type: ident, - $nc_put_varm_type: ident, - ) => { - impl sealed::Sealed for $sized_type {} - #[allow(clippy::use_self)] // False positives - impl NcPutGet for $sized_type { - const NCTYPE: nc_type = $nc_type; - - // fetch ONE value from variable using `$nc_get_var1` - unsafe fn get_var1(variable: &Variable, start: &[usize]) -> error::Result { - let mut buff: MaybeUninit = MaybeUninit::uninit(); - error::checked(super::with_lock(|| { - $nc_get_var1_type( - variable.ncid, - variable.varid, - start.as_ptr(), - buff.as_mut_ptr(), - ) - }))?; - Ok(buff.assume_init()) - } - - // put a SINGLE value into a netCDF variable at the given index - unsafe fn put_var1( - variable: &mut VariableMut, - start: &[usize], - value: Self, - ) -> error::Result<()> { - error::checked(super::with_lock(|| { - $nc_put_var1_type( - variable.ncid, - variable.varid, - start.as_ptr(), - addr_of!(value), - ) - })) - } - - unsafe fn get_vara( - variable: &Variable, - start: &[usize], - count: &[usize], - values: *mut Self, - ) -> error::Result<()> { - error::checked(super::with_lock(|| { - $nc_get_vara_type( - variable.ncid, - variable.varid, - start.as_ptr(), - count.as_ptr(), - values, - ) - })) - } - - // put a SLICE of values into a netCDF variable at the given index - unsafe fn put_vara( - variable: &mut VariableMut, - start: &[usize], - count: &[usize], - values: &[Self], - ) -> error::Result<()> { - error::checked(super::with_lock(|| { - $nc_put_vara_type( - variable.ncid, - variable.varid, - start.as_ptr(), - count.as_ptr(), - values.as_ptr(), - ) - })) - } - - unsafe fn get_vars( - variable: &Variable, - start: &[usize], - count: &[usize], - strides: &[isize], - values: *mut Self, - ) -> error::Result<()> { - error::checked(super::with_lock(|| { - $nc_get_vars_type( - variable.ncid, - variable.varid, - start.as_ptr(), - count.as_ptr(), - strides.as_ptr(), - values, - ) - })) - } - - unsafe fn put_vars( - variable: &mut VariableMut, - start: &[usize], - count: &[usize], - stride: &[isize], - values: *const Self, - ) -> error::Result<()> { - error::checked(super::with_lock(|| { - $nc_put_vars_type( - variable.ncid, - variable.varid, - start.as_ptr(), - count.as_ptr(), - stride.as_ptr(), - values, - ) - })) - } - - unsafe fn get_varm( - variable: &Variable, - start: &[usize], - count: &[usize], - stride: &[isize], - map: &[isize], - values: *mut Self, - ) -> error::Result<()> { - error::checked(super::with_lock(|| { - $nc_get_varm_type( - variable.ncid, - variable.varid, - start.as_ptr(), - count.as_ptr(), - stride.as_ptr(), - map.as_ptr(), - values, - ) - })) - } - - unsafe fn put_varm( - variable: &mut VariableMut, - start: &[usize], - count: &[usize], - stride: &[isize], - map: &[isize], - values: *const Self, - ) -> error::Result<()> { - error::checked(super::with_lock(|| { - $nc_put_varm_type( - variable.ncid, - variable.varid, - start.as_ptr(), - count.as_ptr(), - stride.as_ptr(), - map.as_ptr(), - values, - ) - })) - } - } - }; -} -impl_numeric!( - u8, - NC_UBYTE, - nc_get_var_uchar, - nc_get_vara_uchar, - nc_get_var1_uchar, - nc_put_var1_uchar, - nc_put_vara_uchar, - nc_get_vars_uchar, - nc_put_vars_uchar, - nc_get_varm_uchar, - nc_put_varm_uchar, -); - -impl_numeric!( - i8, - NC_BYTE, - nc_get_var_schar, - nc_get_vara_schar, - nc_get_var1_schar, - nc_put_var1_schar, - nc_put_vara_schar, - nc_get_vars_schar, - nc_put_vars_schar, - nc_get_varm_schar, - nc_put_varm_schar, -); - -impl_numeric!( - i16, - NC_SHORT, - nc_get_var_short, - nc_get_vara_short, - nc_get_var1_short, - nc_put_var1_short, - nc_put_vara_short, - nc_get_vars_short, - nc_put_vars_short, - nc_get_varm_short, - nc_put_varm_short, -); - -impl_numeric!( - u16, - NC_USHORT, - nc_get_var_ushort, - nc_get_vara_ushort, - nc_get_var1_ushort, - nc_put_var1_ushort, - nc_put_vara_ushort, - nc_get_vars_ushort, - nc_put_vars_ushort, - nc_get_varm_ushort, - nc_put_varm_ushort, -); - -impl_numeric!( - i32, - NC_INT, - nc_get_var_int, - nc_get_vara_int, - nc_get_var1_int, - nc_put_var1_int, - nc_put_vara_int, - nc_get_vars_int, - nc_put_vars_int, - nc_get_varm_int, - nc_put_varm_int, -); - -impl_numeric!( - u32, - NC_UINT, - nc_get_var_uint, - nc_get_vara_uint, - nc_get_var1_uint, - nc_put_var1_uint, - nc_put_vara_uint, - nc_get_vars_uint, - nc_put_vars_uint, - nc_get_varm_uint, - nc_put_varm_uint, -); - -impl_numeric!( - i64, - NC_INT64, - nc_get_var_longlong, - nc_get_vara_longlong, - nc_get_var1_longlong, - nc_put_var1_longlong, - nc_put_vara_longlong, - nc_get_vars_longlong, - nc_put_vars_longlong, - nc_get_varm_longlong, - nc_put_varm_longlong, -); - -impl_numeric!( - u64, - NC_UINT64, - nc_get_var_ulonglong, - nc_get_vara_ulonglong, - nc_get_var1_ulonglong, - nc_put_var1_ulonglong, - nc_put_vara_ulonglong, - nc_get_vars_ulonglong, - nc_put_vars_ulonglong, - nc_get_varm_ulonglong, - nc_put_varm_ulonglong, -); - -impl_numeric!( - f32, - NC_FLOAT, - nc_get_var_float, - nc_get_vara_float, - nc_get_var1_float, - nc_put_var1_float, - nc_put_vara_float, - nc_get_vars_float, - nc_put_vars_float, - nc_get_varm_float, - nc_put_varm_float, -); - -impl_numeric!( - f64, - NC_DOUBLE, - nc_get_var_double, - nc_get_vara_double, - nc_get_var1_double, - nc_put_var1_double, - nc_put_vara_double, - nc_get_vars_double, - nc_put_vars_double, - nc_get_varm_double, - nc_put_varm_double, -); - -/// Holds the contents of a netcdf string. Use deref to get a `CStr` -struct NcString { - data: *mut c_char, -} -impl NcString { - /// Create an `NcString` - /// - /// TODO: Change signature to c_char or remove - unsafe fn from_ptr(ptr: *mut i8) -> Self { - Self { data: ptr.cast() } - } -} -impl Drop for NcString { - fn drop(&mut self) { - unsafe { - error::checked(super::with_lock(|| nc_free_string(1, &mut self.data))).unwrap(); - } - } -} -impl std::ops::Deref for NcString { - type Target = CStr; - fn deref(&self) -> &Self::Target { - unsafe { CStr::from_ptr(self.data) } - } -} - impl<'g> VariableMut<'g> { /// Adds an attribute to the variable pub fn put_attribute(&mut self, name: &str, val: T) -> error::Result @@ -740,66 +267,7 @@ impl<'g> VariableMut<'g> { } impl<'g> Variable<'g> { - fn value_mono(&self, extent: &Extents) -> error::Result { - let dims = self.dimensions(); - let (start, count, _stride) = extent.get_start_count_stride(dims)?; - - let number_of_items = count.iter().copied().fold(1_usize, usize::saturating_mul); - if number_of_items != 1 { - return Err(error::Error::BufferLen { - wanted: 1, - actual: number_of_items, - }); - } - - unsafe { T::get_var1(self, &start) } - } - - /// Fetches one specific value at specific indices - /// indices must has the same length as self.dimensions. - pub fn get_value(&self, indices: E) -> error::Result - where - E: TryInto, - E::Error: Into, - { - let extent = indices.try_into().map_err(Into::into)?; - self.value_mono(&extent) - } - - fn string_value_mono(&self, extent: &Extents) -> error::Result { - let dims = self.dimensions(); - let (start, count, _stride) = extent.get_start_count_stride(dims)?; - - let number_of_items = count.iter().copied().fold(1_usize, usize::saturating_mul); - if number_of_items != 1 { - return Err(error::Error::BufferLen { - wanted: 1, - actual: number_of_items, - }); - } - - let mut s: *mut std::os::raw::c_char = std::ptr::null_mut(); - unsafe { - error::checked(super::with_lock(|| { - nc_get_var1_string(self.ncid, self.varid, start.as_ptr(), &mut s) - }))?; - } - let string = unsafe { NcString::from_ptr(s.cast()) }; - Ok(string.to_string_lossy().into_owned()) - } - - /// Reads a string variable. This involves two copies per read, and should - /// be avoided in performance critical code - pub fn get_string(&self, indices: E) -> error::Result - where - E: TryInto, - E::Error: Into, - { - let extent = indices.try_into().map_err(Into::into)?; - self.string_value_mono(&extent) - } - - fn values_mono(&self, extents: &Extents) -> error::Result> { + fn get_values_mono(&self, extents: &Extents) -> error::Result> { let dims = self.dimensions(); let (start, count, stride) = extents.get_start_count_stride(dims)?; @@ -807,7 +275,14 @@ impl<'g> Variable<'g> { let mut values = Vec::with_capacity(number_of_elements); unsafe { - T::get_vars(self, &start, &count, &stride, values.as_mut_ptr())?; + super::putget::get_vars( + self, + &T::type_descriptor(), + &start, + &count, + &stride, + values.as_mut_ptr(), + )?; values.set_len(number_of_elements); }; Ok(values) @@ -831,18 +306,59 @@ impl<'g> Variable<'g> { /// # Result::<(), netcdf::Error>::Ok(()) /// ``` /// where `Option::transpose` is used to bubble up any read errors - pub fn get_values(&self, extents: E) -> error::Result> + pub fn get_values(&self, extents: E) -> error::Result> where E: TryInto, E::Error: Into, { let extents: Extents = extents.try_into().map_err(Into::into)?; - self.values_mono(&extents) + self.get_values_mono(&extents) + } + + /// Get a single value + pub fn get_value(&self, extents: E) -> error::Result + where + E: TryInto, + E::Error: Into, + { + let mut elems = self.get_values::(extents)?; + if elems.is_empty() { + return Err("No elements returned".into()); + } + if elems.len() > 1 { + return Err("Too many elements returned".into()); + } + Ok(elems.pop().unwrap()) + } + + /// Get a string from this variable + pub fn get_string(&self, extents: E) -> error::Result + where + E: TryInto, + E::Error: Into, + { + let extents = extents.try_into().map_err(Into::into)?; + let mut elems = self.get_values_mono::(&extents)?; + if elems.is_empty() { + return Err("No elements returned".into()); + } + if elems.len() > 1 { + super::utils::checked_with_lock(|| unsafe { + netcdf_sys::nc_free_string(elems.len(), elems.as_mut_ptr().cast()) + })?; + return Err("Too many elements returned".into()); + } + let cstr = unsafe { std::ffi::CStr::from_ptr(elems[0].0) }; + let s = cstr.to_string_lossy().to_string(); + super::utils::checked_with_lock(|| unsafe { + netcdf_sys::nc_free_string(elems.len(), elems.as_mut_ptr().cast()) + })?; + Ok(s) } #[cfg(feature = "ndarray")] /// Fetches variable - fn values_arr_mono(&self, extents: &Extents) -> error::Result> { + fn values_arr_mono(&self, extents: &Extents) -> error::Result> { let dims = self.dimensions(); let mut start = vec![]; let mut count = vec![]; @@ -860,8 +376,15 @@ impl<'g> Variable<'g> { let number_of_elements = count.iter().copied().fold(1_usize, usize::saturating_mul); let mut values = Vec::with_capacity(number_of_elements); + super::putget::get_vars( + self, + &T::type_descriptor(), + &start, + &count, + &stride, + values.as_mut_ptr(), + )?; unsafe { - T::get_vars(self, &start, &count, &stride, values.as_mut_ptr())?; values.set_len(number_of_elements); }; @@ -870,7 +393,7 @@ impl<'g> Variable<'g> { #[cfg(feature = "ndarray")] /// Get values from a variable - pub fn get(&self, extents: E) -> error::Result> + pub fn get(&self, extents: E) -> error::Result> where E: TryInto, E::Error: Into, @@ -881,7 +404,7 @@ impl<'g> Variable<'g> { #[cfg(feature = "ndarray")] /// Get values from a variable directly into an ndarray - pub fn get_into( + pub fn get_into( &self, extents: E, mut out: ndarray::ArrayViewMut, @@ -891,7 +414,7 @@ impl<'g> Variable<'g> { E: TryInto, E::Error: Into, { - let extents = extents.try_into().map_err(|e| e.into())?; + let extents = extents.try_into().map_err(Into::into)?; let dims = self.dimensions(); let mut start = Vec::with_capacity(dims.len()); @@ -920,9 +443,7 @@ impl<'g> Variable<'g> { return Err(("Output array dimensionality is larger than extents").into()); } - let slice = if let Some(slice) = out.as_slice_mut() { - slice - } else { + let Some(slice) = out.as_slice_mut() else { return Err("Output array must be in standard layout".into()); }; @@ -935,22 +456,25 @@ impl<'g> Variable<'g> { // Safety: // start, count, stride are correct length // slice is valid pointer, with enough space to hold all elements - unsafe { - T::get_vars(self, &start, &count, &stride, slice.as_mut_ptr())?; - } - - Ok(()) + super::putget::get_vars( + self, + &T::type_descriptor(), + &start, + &count, + &stride, + slice.as_mut_ptr(), + ) } /// Get the fill value of a variable - pub fn fill_value(&self) -> error::Result> { - if T::NCTYPE != self.vartype { + pub fn fill_value(&self) -> error::Result> { + if T::type_descriptor() != super::types::read_type(self.ncid, self.vartype)? { return Err(error::Error::TypeMismatch); } let mut location = std::mem::MaybeUninit::uninit(); let mut nofill: nc_type = 0; unsafe { - error::checked(super::with_lock(|| { + error::checked(utils::with_lock(|| { nc_inq_var_fill( self.ncid, self.varid, @@ -966,7 +490,7 @@ impl<'g> Variable<'g> { Ok(Some(unsafe { location.assume_init() })) } - fn values_to_mono( + fn values_to_mono( &self, buffer: &mut [T], extents: &Extents, @@ -981,11 +505,22 @@ impl<'g> Variable<'g> { actual: buffer.len(), }); } - unsafe { T::get_vars(self, &start, &count, &stride, buffer.as_mut_ptr()) } + super::putget::get_vars( + self, + &T::type_descriptor(), + &start, + &count, + &stride, + buffer.as_mut_ptr(), + ) } /// Fetches variable into slice /// buffer must be able to hold all the requested elements - pub fn get_values_into(&self, buffer: &mut [T], extents: E) -> error::Result<()> + pub fn get_values_into( + &self, + buffer: &mut [T], + extents: E, + ) -> error::Result<()> where E: TryInto, E::Error: Into, @@ -993,171 +528,10 @@ impl<'g> Variable<'g> { let extents: Extents = extents.try_into().map_err(Into::into)?; self.values_to_mono(buffer, &extents) } - - fn raw_values_mono(&self, buf: &mut [u8], extents: &Extents) -> error::Result<()> { - let dims = self.dimensions(); - let (start, count, stride) = extents.get_start_count_stride(dims)?; - - let typ = self.vartype(); - match typ { - super::types::VariableType::String | super::types::VariableType::Vlen(_) => { - return Err(error::Error::TypeMismatch) - } - _ => (), - } - - let number_of_elements = count.iter().copied().fold(1_usize, usize::saturating_mul); - let bytes_requested = number_of_elements.saturating_mul(typ.size()); - if buf.len() != bytes_requested { - return Err(error::Error::BufferLen { - wanted: buf.len(), - actual: bytes_requested, - }); - } - - error::checked(super::with_lock(|| unsafe { - nc_get_vars( - self.ncid, - self.varid, - start.as_ptr(), - count.as_ptr(), - stride.as_ptr(), - buf.as_mut_ptr().cast(), - ) - })) - } - /// Get values of any type as bytes, with no further interpretation - /// of the values. - /// - /// # Note - /// - /// When working with compound types, variable length arrays and - /// strings will be allocated in `buf`, and this library will - /// not keep track of the allocations. - /// This can lead to memory leaks. - pub fn get_raw_values(&self, buf: &mut [u8], extents: E) -> error::Result<()> - where - E: TryInto, - E::Error: Into, - { - let extents: Extents = extents.try_into().map_err(Into::into)?; - self.raw_values_mono(buf, &extents) - } - - fn vlen_mono(&self, extent: &Extents) -> error::Result> { - let dims = self.dimensions(); - let (start, count, _stride) = extent.get_start_count_stride(dims)?; - - let number_of_items = count.iter().copied().fold(1_usize, usize::saturating_mul); - if number_of_items != 1 { - return Err(error::Error::BufferLen { - wanted: 1, - actual: number_of_items, - }); - } - if let super::types::VariableType::Vlen(v) = self.vartype() { - if v.typ().id() != T::NCTYPE { - return Err(error::Error::TypeMismatch); - } - } else { - return Err(error::Error::TypeMismatch); - }; - - let mut vlen: MaybeUninit = MaybeUninit::uninit(); - - error::checked(super::with_lock(|| unsafe { - nc_get_vara( - self.ncid, - self.varid, - start.as_ptr(), - count.as_ptr(), - vlen.as_mut_ptr().cast(), - ) - }))?; - - let mut vlen = unsafe { vlen.assume_init() }; - - let mut v = Vec::::with_capacity(vlen.len); - - unsafe { - std::ptr::copy_nonoverlapping(vlen.p as *const T, v.as_mut_ptr(), vlen.len); - v.set_len(vlen.len); - } - error::checked(super::with_lock(|| unsafe { nc_free_vlen(&mut vlen) })).unwrap(); - - Ok(v) - } - /// Get a vlen element - pub fn get_vlen(&self, indices: E) -> error::Result> - where - E: TryInto, - E::Error: Into, - { - let extent = indices.try_into().map_err(Into::into)?; - self.vlen_mono(&extent) - } } impl<'g> VariableMut<'g> { - fn put_value_mono(&mut self, value: T, extents: &Extents) -> error::Result<()> { - let dims = self.dimensions(); - let (start, count, _stride) = extents.get_start_count_stride(dims)?; - - let number_of_items = count.iter().copied().fold(1_usize, usize::saturating_mul); - if number_of_items != 1 { - return Err(error::Error::BufferLen { - wanted: 1, - actual: number_of_items, - }); - } - - unsafe { T::put_var1(self, &start, value) } - } - /// Put a single value at `indices` - pub fn put_value(&mut self, value: T, extents: E) -> error::Result<()> - where - E: TryInto, - E::Error: Into, - { - let extents: Extents = extents.try_into().map_err(Into::into)?; - self.put_value_mono(value, &extents) - } - - fn put_string_mono(&mut self, value: &str, extent: &Extents) -> error::Result<()> { - let dims = self.dimensions(); - let (start, count, _stride) = extent.get_start_count_stride(dims)?; - - let number_of_items = count.iter().copied().fold(1_usize, usize::saturating_mul); - if number_of_items != 1 { - return Err(error::Error::BufferLen { - wanted: 1, - actual: number_of_items, - }); - } - - let value = std::ffi::CString::new(value).expect("String contained interior 0"); - let mut ptr = value.as_ptr(); - - unsafe { - error::checked(super::with_lock(|| { - nc_put_var1_string(self.ncid, self.varid, start.as_ptr(), &mut ptr) - }))?; - } - - Ok(()) - } - /// Internally converts to a `CString`, avoid using this function when performance - /// is important - pub fn put_string(&mut self, value: &str, extent: E) -> error::Result<()> - where - E: TryInto, - E::Error: Into, - { - let extents: Extents = extent.try_into().map_err(Into::into)?; - self.put_string_mono(value, &extents) - } - - fn put_values_mono( + fn put_values_mono( &mut self, values: &[T], extents: &Extents, @@ -1178,13 +552,22 @@ impl<'g> VariableMut<'g> { } } - unsafe { - T::put_vars(self, &start, &count, &stride, values.as_ptr())?; - }; + crate::putget::put_vars( + self, + &T::type_descriptor(), + &start, + &count, + &stride, + values.as_ptr(), + )?; Ok(()) } /// Put a slice of values at `indices` - pub fn put_values(&mut self, values: &[T], extents: E) -> error::Result<()> + pub fn put_values( + &mut self, + values: &[T], + extents: E, + ) -> error::Result<()> where E: TryInto, E::Error: Into, @@ -1192,6 +575,24 @@ impl<'g> VariableMut<'g> { let extents: Extents = extents.try_into().map_err(Into::into)?; self.put_values_mono(values, &extents) } + /// Put a value at the specified indices + pub fn put_value(&mut self, value: T, extents: E) -> error::Result<()> + where + E: TryInto, + E::Error: Into, + { + self.put_values(&[value], extents) + } + /// Put a string at the specified indices + pub fn put_string(&mut self, value: &str, extents: E) -> error::Result<()> + where + E: TryInto, + E::Error: Into, + { + let cstr = std::ffi::CString::new(value)?; + let item = super::types::NcString(cstr.as_ptr().cast_mut().cast()); + self.put_value(item, extents) + } /// Set a Fill Value /// @@ -1201,13 +602,13 @@ impl<'g> VariableMut<'g> { #[allow(clippy::needless_pass_by_value)] // All values will be small pub fn set_fill_value(&mut self, fill_value: T) -> error::Result<()> where - T: NcPutGet, + T: NcTypeDescriptor, { - if T::NCTYPE != self.vartype { + if T::type_descriptor() != super::types::read_type(self.ncid, self.vartype)? { return Err(error::Error::TypeMismatch); } unsafe { - error::checked(super::with_lock(|| { + error::checked(utils::with_lock(|| { nc_def_var_fill( self.ncid, self.varid, @@ -1232,7 +633,7 @@ impl<'g> VariableMut<'g> { /// will read potentially uninitialized data. Normally /// one will expect to find some filler value pub unsafe fn set_nofill(&mut self) -> error::Result<()> { - error::checked(super::with_lock(|| { + error::checked(utils::with_lock(|| { nc_def_var_fill(self.ncid, self.varid, NC_NOFILL, std::ptr::null_mut()) })) } @@ -1252,113 +653,17 @@ impl<'g> VariableMut<'g> { Endianness::Big => NC_ENDIAN_BIG, }; unsafe { - error::checked(super::with_lock(|| { + error::checked(utils::with_lock(|| { nc_def_var_endian(self.ncid, self.varid, endianness) }))?; } Ok(()) } - unsafe fn put_raw_values_mono(&mut self, buf: &[u8], extents: &Extents) -> error::Result<()> { - let dims = self.dimensions(); - let (start, count, stride) = extents.get_start_count_stride(dims)?; - - let typ = self.vartype(); - match typ { - super::types::VariableType::String | super::types::VariableType::Vlen(_) => { - return Err(error::Error::TypeMismatch) - } - _ => (), - } - - let number_of_elements = count.iter().copied().fold(1_usize, usize::saturating_mul); - let bytes_requested = number_of_elements.saturating_mul(typ.size()); - if buf.len() != bytes_requested { - return Err(error::Error::BufferLen { - wanted: buf.len(), - actual: bytes_requested, - }); - } - - #[allow(unused_unsafe)] - error::checked(super::with_lock(|| unsafe { - nc_put_vars( - self.ncid, - self.varid, - start.as_ptr(), - count.as_ptr(), - stride.as_ptr(), - buf.as_ptr().cast(), - ) - })) - } - /// Get values of any type as bytes - /// - /// # Safety - /// - /// When working with compound types, variable length arrays and - /// strings create pointers from the buffer, and tries to copy - /// memory from these locations. Compound types which does not - /// have these elements will be safe to access, and can treat - /// this function as safe - pub unsafe fn put_raw_values(&mut self, buf: &[u8], extents: E) -> error::Result<()> - where - E: TryInto, - E::Error: Into, - { - let extents: Extents = extents.try_into().map_err(Into::into)?; - self.put_raw_values_mono(buf, &extents) - } - - fn put_vlen_mono(&mut self, vec: &[T], extent: &Extents) -> error::Result<()> { - let dims = self.dimensions(); - let (start, count, stride) = extent.get_start_count_stride(dims)?; - - let number_of_items = count.iter().copied().fold(1_usize, usize::saturating_mul); - if number_of_items != 1 { - return Err(error::Error::BufferLen { - wanted: 1, - actual: number_of_items, - }); - } - - if let super::types::VariableType::Vlen(v) = self.vartype() { - if v.typ().id() != T::NCTYPE { - return Err(error::Error::TypeMismatch); - } - } else { - return Err(error::Error::TypeMismatch); - }; - - let vlen = nc_vlen_t { - len: vec.len(), - p: vec.as_ptr().cast_mut().cast(), - }; - - error::checked(super::with_lock(|| unsafe { - nc_put_vars( - self.ncid, - self.varid, - start.as_ptr(), - count.as_ptr(), - stride.as_ptr(), - std::ptr::addr_of!(vlen).cast(), - ) - })) - } - /// Get a vlen element - pub fn put_vlen(&mut self, vec: &[T], indices: E) -> error::Result<()> - where - E: TryInto, - E::Error: Into, - { - let extent = indices.try_into().map_err(Into::into)?; - self.put_vlen_mono(vec, &extent) - } - #[cfg(feature = "ndarray")] /// Put values in an ndarray into the variable - pub fn put( + #[allow(clippy::needless_pass_by_value)] + pub fn put( &mut self, extent: E, arr: ndarray::ArrayView, @@ -1368,11 +673,9 @@ impl<'g> VariableMut<'g> { E::Error: Into, D: ndarray::Dimension, { - let extent = extent.try_into().map_err(|e| e.into())?; + let extent = extent.try_into().map_err(Into::into)?; - let slice = if let Some(slice) = arr.as_slice() { - slice - } else { + let Some(slice) = arr.as_slice() else { return Err( "Slice is not contiguous or in c-order, you might want to use `as_standard_layout`" .into(), @@ -1429,14 +732,21 @@ impl<'g> VariableMut<'g> { // Dimensionality matches (always pushing in for loop) // slice is valid pointer since we assert the size above // slice is valid pointer since memory order is standard_layout (C) - unsafe { T::put_vars(self, &start, &count, &stride, slice.as_ptr()) } + super::putget::put_vars::( + self, + &self.vartype(), + &start, + &count, + &stride, + slice.as_ptr(), + ) } } impl<'g> VariableMut<'g> { pub(crate) fn add_from_str( ncid: nc_type, - xtype: nc_type, + xtype: &NcVariableType, name: &str, dims: &[&str], ) -> error::Result { @@ -1453,9 +763,10 @@ impl<'g> VariableMut<'g> { let cname = super::utils::short_name_to_bytes(name)?; let mut varid = 0; + let xtype = crate::types::find_type(ncid, xtype)?.expect("Type not found"); unsafe { let dimlen = dimensions.len().try_into()?; - error::checked(super::with_lock(|| { + error::checked(utils::with_lock(|| { nc_def_var( ncid, cname.as_ptr().cast(), @@ -1494,20 +805,20 @@ pub(crate) fn variables_at_ncid<'g>( ) -> error::Result>>> { let mut nvars = 0; unsafe { - error::checked(super::with_lock(|| { + error::checked(utils::with_lock(|| { nc_inq_varids(ncid, &mut nvars, std::ptr::null_mut()) }))?; } let mut varids = vec![0; nvars.try_into()?]; unsafe { - error::checked(super::with_lock(|| { + error::checked(utils::with_lock(|| { nc_inq_varids(ncid, std::ptr::null_mut(), varids.as_mut_ptr()) }))?; } Ok(varids.into_iter().map(move |varid| { let mut xtype = 0; unsafe { - error::checked(super::with_lock(|| nc_inq_vartype(ncid, varid, &mut xtype)))?; + error::checked(utils::with_lock(|| nc_inq_vartype(ncid, varid, &mut xtype)))?; } let dimensions = super::dimension::dimensions_from_variable(ncid, varid)? .collect::>>()?; @@ -1540,7 +851,7 @@ pub(crate) fn add_variable_from_identifiers<'g>( } let mut dimlen = 0; unsafe { - error::checked(super::with_lock(|| { + error::checked(utils::with_lock(|| { nc_inq_dimlen(id.ncid, id.dimid, &mut dimlen) }))?; } @@ -1556,7 +867,7 @@ pub(crate) fn add_variable_from_identifiers<'g>( let mut varid = 0; unsafe { let dimlen = dims.len().try_into()?; - error::checked(super::with_lock(|| { + error::checked(utils::with_lock(|| { nc_def_var( ncid, cname.as_ptr().cast(), diff --git a/netcdf/tests/attributes.rs b/netcdf/tests/attributes.rs index d7f9b0e..e473207 100644 --- a/netcdf/tests/attributes.rs +++ b/netcdf/tests/attributes.rs @@ -27,7 +27,10 @@ fn attributes_read() { .expect("Could not add variable"); var.put_attribute("att", "some attribute") .expect("Could not add attribute"); - assert!(var.vartype().as_basic().unwrap().is_f32()); + assert_eq!( + var.vartype(), + netcdf::types::NcVariableType::Float(netcdf::types::FloatType::F32) + ); for attr in var.attributes() { attr.value().unwrap(); diff --git a/netcdf/tests/group.rs b/netcdf/tests/group.rs index ef45637..6d60de2 100644 --- a/netcdf/tests/group.rs +++ b/netcdf/tests/group.rs @@ -57,7 +57,10 @@ fn find_variable() { assert_eq!(v.len(), 1); let z = group.variable_mut("z").unwrap(); assert_eq!(z.dimensions()[0].len(), 3); - assert!(z.vartype().as_basic().unwrap().is_u8()); + assert_eq!( + z.vartype(), + netcdf::types::NcVariableType::Int(netcdf::types::IntType::U8) + ); assert_eq!(z.name(), "z"); assert!(group.variable("vvvvv").is_none()); diff --git a/netcdf/tests/lib.rs b/netcdf/tests/lib.rs index e714983..fe62a93 100644 --- a/netcdf/tests/lib.rs +++ b/netcdf/tests/lib.rs @@ -982,11 +982,7 @@ fn unlimited_dimension_single_putting() { fn check_equal(var: &netcdf::Variable, check: &[T]) where - T: netcdf::NcPutGet - + std::clone::Clone - + std::default::Default - + std::fmt::Debug - + std::cmp::PartialEq, + T: netcdf::NcTypeDescriptor + Copy + Clone + Default + std::fmt::Debug + PartialEq, { let mut v: Vec = vec![Default::default(); check.len()]; var.get_values_into(&mut v, ..).unwrap(); @@ -1115,7 +1111,9 @@ fn string_variables() { file.add_unlimited_dimension("x").unwrap(); file.add_dimension("y", 2).unwrap(); - let var = &mut file.add_string_variable("str", &["x"]).unwrap(); + let var = &mut file + .add_variable_with_type("str", &["x"], &netcdf::types::NcVariableType::String) + .unwrap(); var.put_string("Hello world!", 0).unwrap(); var.put_string("Trying a very long string just to see how that goes", [2]) diff --git a/netcdf/tests/types.rs b/netcdf/tests/types.rs index efe13a1..b3d09bc 100644 --- a/netcdf/tests/types.rs +++ b/netcdf/tests/types.rs @@ -1,9 +1,19 @@ +use netcdf::types::*; + mod common; #[test] -fn test_roundtrip_types() { +fn test_empty_basic_types() { let d = tempfile::tempdir().unwrap(); let path = d.path().join("test_roundtrip_types.nc"); + #[repr(transparent)] + struct NcString(*const std::ffi::c_char); + + unsafe impl NcTypeDescriptor for NcString { + fn type_descriptor() -> NcVariableType { + NcVariableType::String + } + } { let mut file = netcdf::create(&path).unwrap(); file.add_variable::("i8", &[]).unwrap(); @@ -16,56 +26,47 @@ fn test_roundtrip_types() { file.add_variable::("u64", &[]).unwrap(); file.add_variable::("f32", &[]).unwrap(); file.add_variable::("f64", &[]).unwrap(); - file.add_string_variable("string", &[]).unwrap(); + file.add_variable::("string", &[]).unwrap(); } let file = netcdf::open(&path).unwrap(); assert_eq!(file.types().unwrap().count(), 0); + let root = file.root().unwrap(); assert_eq!(root.types().count(), 0); for var in file.variables() { match var.name().as_str() { "i8" => { - assert!(var.vartype().as_basic().unwrap().is_i8()); - assert!(var.vartype().is_i8()); + assert_eq!(var.vartype(), NcVariableType::Int(IntType::I8)); } "u8" => { - assert!(var.vartype().as_basic().unwrap().is_u8()); - assert!(var.vartype().is_u8()); + assert_eq!(var.vartype(), NcVariableType::Int(IntType::U8)); } "i16" => { - assert!(var.vartype().as_basic().unwrap().is_i16()); - assert!(var.vartype().is_i16()); + assert_eq!(var.vartype(), NcVariableType::Int(IntType::I16)); } "u16" => { - assert!(var.vartype().as_basic().unwrap().is_u16()); - assert!(var.vartype().is_u16()); + assert_eq!(var.vartype(), NcVariableType::Int(IntType::U16)); } "i32" => { - assert!(var.vartype().as_basic().unwrap().is_i32()); - assert!(var.vartype().is_i32()); + assert_eq!(var.vartype(), NcVariableType::Int(IntType::I32)); } "u32" => { - assert!(var.vartype().as_basic().unwrap().is_u32()); - assert!(var.vartype().is_u32()); + assert_eq!(var.vartype(), NcVariableType::Int(IntType::U32)); } "i64" => { - assert!(var.vartype().as_basic().unwrap().is_i64()); - assert!(var.vartype().is_i64()); + assert_eq!(var.vartype(), NcVariableType::Int(IntType::I64)); } "u64" => { - assert!(var.vartype().as_basic().unwrap().is_u64()); - assert!(var.vartype().is_u64()); + assert_eq!(var.vartype(), NcVariableType::Int(IntType::U64)); } "f32" => { - assert!(var.vartype().as_basic().unwrap().is_f32()); - assert!(var.vartype().is_f32()); + assert_eq!(var.vartype(), NcVariableType::Float(FloatType::F32)); } "f64" => { - assert!(var.vartype().as_basic().unwrap().is_f64()); - assert!(var.vartype().is_f64()); + assert_eq!(var.vartype(), NcVariableType::Float(FloatType::F64)); } - "string" => assert!(var.vartype().is_string()), + "string" => assert_eq!(var.vartype(), NcVariableType::String), _ => panic!("Got an unexpected varname: {}", var.name()), } } @@ -76,21 +77,33 @@ fn add_opaque() { let d = tempfile::tempdir().unwrap(); let path = d.path().join("test_opaque.nc"); - { - let mut file = netcdf::create(path).unwrap(); + #[repr(transparent)] + struct Foo([u8; 2]); - let typ = file.add_opaque_type("opa", 42).unwrap(); - assert_eq!(&typ.name(), "opa"); - assert_eq!(typ.size(), 42); + unsafe impl NcTypeDescriptor for Foo { + fn type_descriptor() -> NcVariableType { + NcVariableType::Opaque(OpaqueType { + name: "Foo".to_string(), + size: std::mem::size_of::(), + }) + } + } + + { + let mut file = netcdf::create(&path).unwrap(); + file.add_type::().unwrap(); let mut g = file.add_group("g").unwrap(); - let gtyp = g.add_opaque_type("oma", 43).unwrap(); - assert_eq!(>yp.name(), "oma"); - assert_eq!(gtyp.size(), 43); + g.add_type::().unwrap(); } - // let file = netcdf::open(&path).unwrap(); - // let var = file.typ("opa").unwrap(); + let file = netcdf::open(&path).unwrap(); + let types = file.types().unwrap().collect::>(); + assert_eq!(types, &[Foo::type_descriptor()]); + + let group = file.group("g").unwrap().unwrap(); + let types = group.types().collect::>(); + assert_eq!(types, &[Foo::type_descriptor()]); } #[test] @@ -101,17 +114,30 @@ fn add_vlen() { { let mut file = netcdf::create(path).unwrap(); - let typ = file.add_vlen_type::("v").unwrap(); - assert_eq!(&typ.name(), "v"); - assert!(typ.typ().is_u32()); + let tp = NcVariableType::Vlen(VlenType { + name: "v".to_owned(), + basetype: Box::new(NcVariableType::Int(IntType::U32)), + }); + + file.add_type_from_descriptor(tp.clone()).unwrap(); + + let types = file.types().unwrap().collect::>(); + assert_eq!(types, &[tp]); + let mut g = file.add_group("g").unwrap(); - let typ = g.add_vlen_type::("w").unwrap(); - assert_eq!(&typ.name(), "w"); - assert!(&typ.typ().is_i32()); + let tp = NcVariableType::Vlen(VlenType { + name: "w".to_owned(), + basetype: Box::new(NcVariableType::Int(IntType::I32)), + }); + g.add_type_from_descriptor(tp.clone()).unwrap(); + + let types = g.types().collect::>(); + assert_eq!(types, &[tp]); } } #[test] +#[cfg(feature = "derive")] fn add_enum() { let d = tempfile::tempdir().unwrap(); let path = d.path().join("test_add_enum.nc"); @@ -119,150 +145,229 @@ fn add_enum() { { let mut file = netcdf::create(&path).unwrap(); - let e = file - .add_enum_type::("e", &[("a", 0), ("b", 1), ("c", 2), ("d", 3)]) - .unwrap(); - assert_eq!(&e.name(), "e"); - assert!(e.typ().is_i32()); - for member in e.members::().unwrap() { - match member.0.as_str() { - "a" => assert_eq!(member.1, 0), - "b" => assert_eq!(member.1, 1), - "c" => assert_eq!(member.1, 2), - "d" => assert_eq!(member.1, 3), - _ => panic!(), - } + #[derive(netcdf_derive::NcType)] + #[repr(i32)] + #[allow(non_camel_case_types)] + #[allow(dead_code)] + enum e { + a = 0, + b, + c, + d, + } + + file.add_type::().unwrap(); + { + let types = file.types().unwrap().collect::>(); + assert_eq!(types.len(), 1); + assert_eq!( + types[0], + NcVariableType::Enum(EnumType { + name: "e".to_owned(), + fieldnames: vec![ + "a".to_owned(), + "b".to_owned(), + "c".to_owned(), + "d".to_owned() + ], + fieldvalues: EnumTypeValues::I32(vec![0, 1, 2, 3]) + }) + ) + } + + #[derive(netcdf_derive::NcType)] + #[repr(i64)] + #[allow(non_camel_case_types)] + #[allow(dead_code)] + enum f { + e = -32, + f = 41, + g = 1241232, + h = 0, } - assert_eq!(&e.name_from_value(0).unwrap(), "a"); - assert_eq!(&e.name_from_value(1).unwrap(), "b"); - assert_eq!(&e.name_from_value(2).unwrap(), "c"); - assert_eq!(&e.name_from_value(3).unwrap(), "d"); - assert!(&e.name_from_value(4).is_none()); let mut g = file.add_group("g").unwrap(); - let e = g - .add_enum_type::("e", &[("e", -32), ("f", 41), ("g", 1241232), ("h", 0)]) - .unwrap(); - assert_eq!(&e.name(), "e"); - assert!(e.typ().is_i64()); - for member in e.members::().unwrap() { - match member.0.as_str() { - "e" => assert_eq!(member.1, -32), - "f" => assert_eq!(member.1, 41), - "g" => assert_eq!(member.1, 1241232), - "h" => assert_eq!(member.1, 0), - _ => panic!(), - } + g.add_type::().unwrap(); + { + let types = g.types().collect::>(); + assert_eq!(types.len(), 1); + assert_eq!( + types[0], + NcVariableType::Enum(EnumType { + name: "f".to_owned(), + fieldnames: vec![ + "e".to_owned(), + "f".to_owned(), + "g".to_owned(), + "h".to_owned() + ], + fieldvalues: EnumTypeValues::I64(vec![ + f::e as _, + f::f as _, + f::g as _, + f::h as _ + ]) + }) + ) } - assert_eq!(&e.name_from_value(-32).unwrap(), "e"); - assert_eq!(&e.name_from_value(41).unwrap(), "f"); - assert_eq!(&e.name_from_value(1241232).unwrap(), "g"); - assert_eq!(&e.name_from_value(0).unwrap(), "h"); - assert!(&e.name_from_value(4).is_none()); } } #[test] +#[cfg(feature = "derive")] fn add_compound() { let d = tempfile::tempdir().unwrap(); let path = d.path().join("test_add_compound.nc"); let mut file = netcdf::create(path).unwrap(); - let mut builder = file.add_compound_type("c").unwrap(); - builder.add::("u8").unwrap(); - builder.add::("i8").unwrap(); - builder.add_array::("ai32", &[1, 2, 3]).unwrap(); - - let c = builder.build().unwrap(); - let e = file.add_enum_type("e", &[("a", 1), ("b", 2)]).unwrap(); + #[derive(netcdf_derive::NcType)] + #[repr(C)] + #[netcdf(rename = "c")] + struct C { + u8: u8, + i8: i8, + i16: i16, + } - let mut builder = file.add_compound_type("cc").unwrap(); - builder.add_type("e", &e.into()).unwrap(); - builder.add_type("c", &c.into()).unwrap(); - builder.build().unwrap(); + file.add_type::().unwrap(); + + #[derive(netcdf_derive::NcType)] + #[repr(i32)] + #[netcdf(rename = "e")] + #[allow(dead_code)] + enum E { + #[netcdf(rename = "a")] + A = 1, + #[netcdf(rename = "b")] + B = 2, + } + file.add_type::().unwrap(); + + #[derive(netcdf_derive::NcType)] + #[repr(C)] + #[netcdf(rename = "cc")] + struct CC { + e: E, + c: C, + } + file.add_type::().unwrap(); + + let types = file.types().unwrap().collect::>(); + assert_eq!( + types[0], + NcVariableType::Compound(CompoundType { + name: "c".to_owned(), + size: std::mem::size_of::(), + fields: vec![ + CompoundTypeField { + name: "u8".to_owned(), + basetype: NcVariableType::Int(IntType::U8), + arraydims: None, + offset: 0, + }, + CompoundTypeField { + name: "i8".to_owned(), + basetype: NcVariableType::Int(IntType::I8), + arraydims: None, + offset: 1, + }, + CompoundTypeField { + name: "i16".to_owned(), + basetype: NcVariableType::Int(IntType::I16), + arraydims: None, + offset: 2, + }, + ] + }) + ); + assert_eq!(types[1], E::type_descriptor(),); + assert_eq!(types[2], CC::type_descriptor()); } #[test] +#[cfg(feature = "derive")] fn read_compound_simple_nc4() { - use netcdf::types::VariableType; let path = common::test_location().join("simple_nc4.nc"); let file = netcdf::open(&path).unwrap(); - let group = file.group("grp2").unwrap().unwrap(); - for typ in group.types() { - let c = match typ { - VariableType::Compound(c) => c, - _ => panic!(), - }; - assert_eq!(&c.name(), "sample_compound_type"); - let subtypes = c.fields().collect::>(); - assert_eq!(subtypes.len(), 2); - - assert_eq!(&subtypes[0].name(), "i1"); - assert_eq!(&subtypes[1].name(), "i2"); - assert!(subtypes[0].typ().is_i32()); - assert!(subtypes[1].typ().is_i32()); - - assert_eq!(subtypes[0].offset(), 0); - assert_eq!(subtypes[1].offset(), std::mem::size_of::()); - - assert!(subtypes[0].dimensions().is_none()); - assert!(subtypes[1].dimensions().is_none()); + #[repr(C)] + #[derive(netcdf_derive::NcType, Copy, Clone, Debug, PartialEq, Eq)] + #[netcdf(rename = "sample_compound_type")] + struct SampleCompoundType { + i1: i32, + i2: i32, } + let group = file.group("grp2").unwrap().unwrap(); + let types = group.types().collect::>(); + assert_eq!( + types, + &[NcVariableType::Compound(CompoundType { + name: "sample_compound_type".to_owned(), + size: 8, + fields: vec![ + CompoundTypeField { + name: "i1".to_owned(), + basetype: NcVariableType::Int(IntType::I32), + offset: 0, + arraydims: None, + }, + CompoundTypeField { + name: "i2".to_owned(), + basetype: NcVariableType::Int(IntType::I32), + offset: 4, + arraydims: None, + }, + ] + })] + ); + let var = group.variable("data").unwrap(); - if let VariableType::Compound(_) = var.vartype() { - } else { - panic!(); - } + assert_eq!(var.vartype(), SampleCompoundType::type_descriptor()); - let mut raws = vec![0_u8; 12 * 6 * 2 * 4]; - var.get_raw_values(&mut raws, (..6, ..12)).unwrap(); - - use std::convert::TryInto; - let intlen = 4; - for i in 0..6 * 12 { - let i1 = i32::from_le_bytes(raws[2 * intlen * i..][..intlen].try_into().unwrap()); - let i2 = i32::from_le_bytes( - raws[2 * intlen * i + intlen..][..intlen] - .try_into() - .unwrap(), - ); - assert_eq!(i1, 42); - assert_eq!(i2, -42); - } + let values = var.get_values::(..).unwrap(); + + assert_eq!( + values, + [SampleCompoundType { i1: 42, i2: -42 }].repeat(6 * 12) + ); } #[test] +#[cfg(feature = "derive")] fn put_get_enum() { let d = tempfile::tempdir().unwrap(); let path = d.path().join("test_put_get_enum.nc"); - let bytes = (0..2 * 5).map(|i| i % 3 + 1).collect::>(); + #[derive(netcdf::NcType, Debug, Copy, Clone, PartialEq, Eq)] + #[repr(i8)] + enum E { + #[netcdf(rename = "one")] + One = 1, + #[netcdf(rename = "two")] + Two = 2, + #[netcdf(rename = "three")] + Three = 3, + } + + let bytes = [E::One, E::Two, E::Three].repeat(5); { let mut file = netcdf::create(&path).unwrap(); - let e = file - .add_enum_type("e", &[("one", 1_u8), ("two", 2), ("three", 3)]) - .unwrap(); - file.add_dimension("x", 2).unwrap(); + file.add_type::().unwrap(); + file.add_dimension("x", 4).unwrap(); file.add_dimension("y", 5).unwrap(); - let mut var = file - .add_variable_with_type("var", &["y", "x"], &e.into()) - .unwrap(); + let mut var = file.add_variable::("var", &["y", "x"]).unwrap(); - unsafe { - var.put_raw_values(&bytes, (..5, ..2)).unwrap(); - } + var.put_values(&bytes, (..5, ..3)).unwrap(); } let file = netcdf::open(&path).unwrap(); let var = file.variable("var").unwrap(); - let mut bytes_copy = vec![0_u8; 5 * 2]; - var.get_raw_values(&mut bytes_copy, (..5, ..2)).unwrap(); + let bytes_copy = var.get_values((..5, ..3)).unwrap(); assert_eq!(bytes, bytes_copy); } @@ -271,52 +376,109 @@ fn put_get_vlen() { let d = tempfile::tempdir().unwrap(); let path = d.path().join("test_put_get_enum.nc"); + #[derive(Copy, Clone)] + #[repr(C, packed)] + struct Foo { + len: usize, + p: *mut u8, + } + + unsafe impl NcTypeDescriptor for Foo { + fn type_descriptor() -> NcVariableType { + NcVariableType::Vlen(VlenType { + name: "Foo".to_owned(), + basetype: Box::new(NcVariableType::Int(IntType::U8)), + }) + } + } + { let mut file = netcdf::create(&path).unwrap(); file.add_dimension("x", 9).unwrap(); - let v = file.add_vlen_type::("v").unwrap(); + file.add_type::().unwrap(); - let mut var = file - .add_variable_with_type("var", &["x"], &v.into()) - .unwrap(); + let mut var = file.add_variable::("var", &["x"]).unwrap(); - let buf = (0..9).collect::>(); + let buf = (0..9).collect::>(); + let foo = Foo { + len: buf.len(), + p: buf.as_ptr().cast_mut(), + }; - for i in 0..9 { - var.put_vlen(&buf[i..], [i]).unwrap(); - } + var.put_values(&[foo].repeat(9), ..).unwrap(); } let file = netcdf::open(&path).unwrap(); let var = file.variable("var").unwrap(); - let buf = (0..9).collect::>(); - for i in 0..9 { - let v = var.get_vlen::(&[i]).unwrap(); - assert_eq!(v, &buf[i..]); + let buf = (0..9).collect::>(); + let mut values = var.get_values::(..).unwrap(); + for v in &values { + assert_eq!(&buf, unsafe { std::slice::from_raw_parts(v.p, v.len) }); + } + let errcode; + unsafe { + let _lock = netcdf_sys::libnetcdf_lock.lock(); + errcode = netcdf_sys::nc_free_vlens(values.len(), values.as_mut_ptr().cast()); } + assert_eq!(errcode, netcdf_sys::NC_NOERR); } #[test] fn char() { - use netcdf::types::BasicType; let d = tempfile::tempdir().unwrap(); let path = d.path().join("test_char.nc"); - let mut f = netcdf::create(path).unwrap(); + #[repr(transparent)] + #[derive(Copy, Clone, Debug, PartialEq, Eq)] + struct NcChar(pub i8); + + unsafe impl NcTypeDescriptor for NcChar { + fn type_descriptor() -> NcVariableType { + NcVariableType::Char + } + } + let mut f = netcdf::create(path).unwrap(); f.add_dimension("x", 2).unwrap(); - let mut var = f - .add_variable_with_type("x", &["x"], &BasicType::Char.into()) - .unwrap(); + let mut var = f.add_variable::("x", &["x"]).unwrap(); - let vals = [2, 3]; - unsafe { - var.put_raw_values(&vals, [..vals.len()]).unwrap(); + assert_eq!(var.vartype(), NcVariableType::Char); + + let vals = [NcChar('2' as _), NcChar('3' as _)]; + var.put_values(&vals, [..vals.len()]).unwrap(); + + let retrieved_vals = var.get_values::(0..2).unwrap(); + assert_eq!(vals.as_slice(), retrieved_vals); +} + +#[test] +fn no_subtype() { + #[derive(netcdf::NcType)] + #[repr(C)] + struct Foo { + a: u8, + } + #[derive(netcdf::NcType)] + #[repr(C)] + struct Bar { + a: i8, } + #[derive(netcdf::NcType)] + #[repr(C)] + struct FooBar { + foo: Foo, + bar: Bar, + } + let d = tempfile::tempdir().unwrap(); + let path = d.path().join("test_subtype.nc"); + + let mut file = netcdf::create(path).unwrap(); - let mut retrieved_vals = [0, 0]; - var.get_raw_values(&mut retrieved_vals, 0..2).unwrap(); - assert_eq!(vals, retrieved_vals); + file.add_type::().unwrap_err(); + file.add_type::().unwrap(); + file.add_type::().unwrap_err(); + file.add_type::().unwrap(); + file.add_type::().unwrap(); } From d66d0f6f0ae28b1c1ed9532f73590e884bca4660 Mon Sep 17 00:00:00 2001 From: Magnus Ulimoen Date: Thu, 14 Mar 2024 10:02:28 +0100 Subject: [PATCH 2/4] Skip netcdf-derive for non-nightly CI workflows --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4fe6e15..4801253 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -74,7 +74,7 @@ jobs: run: cargo build --verbose --workspace --exclude netcdf-src - name: Test - run: cargo test --verbose --workspace --exclude netcdf-src + run: cargo test --verbose --workspace --exclude netcdf-src --exclude netcdf-derive conda: name: conda @@ -106,7 +106,7 @@ jobs: export HDF5_DIR="$CONDA_PREFIX" export NETCDF_DIR="$CONDA_PREFIX" [ "${{runner.os}}" != "Windows" ] && export RUSTFLAGS="-C link-args=-Wl,-rpath,$CONDA_PREFIX/lib" - cargo test -vv --workspace --exclude netcdf-src + cargo test -vv --workspace --exclude netcdf-src --exclude netcdf-derive static_builds: name: static builds From da40432ef050e604d18261f2def7a58b4bac0ad7 Mon Sep 17 00:00:00 2001 From: Magnus Ulimoen Date: Thu, 14 Mar 2024 10:36:27 +0100 Subject: [PATCH 3/4] Do not run doc test (only compile them) --- netcdf/src/types.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/netcdf/src/types.rs b/netcdf/src/types.rs index d980078..2b840ad 100644 --- a/netcdf/src/types.rs +++ b/netcdf/src/types.rs @@ -14,7 +14,7 @@ use crate::{error::Result, utils::checked_with_lock}; /// user defined types. With the `derive` feature enabled for this crate one can /// easily define types for reading and writing to and from `netCDF` files. /// # Example (derive macro) -/// ```rust +/// ```rust,no_run /// # #[cfg(feature = "derive")] /// #[repr(C)] /// #[derive(netcdf::NcType, Debug, Copy, Clone)] @@ -64,7 +64,7 @@ use crate::{error::Result, utils::checked_with_lock}; /// Reading of an `netcdf_sys::NC_CHAR` can not be done by using `i8` or `u8` as /// such types are not considered text. The below snippet can be used to define /// a type which will read this type. -/// ```rust +/// ```rust,no_run /// # use netcdf::types::*; /// #[repr(transparent)] /// #[derive(Copy, Clone)] @@ -76,7 +76,7 @@ use crate::{error::Result, utils::checked_with_lock}; /// } /// ``` /// ## Opaque type -/// ```rust +/// ```rust,no_run /// # use netcdf::types::*; /// #[repr(transparent)] /// #[derive(Copy, Clone)] @@ -95,7 +95,7 @@ use crate::{error::Result, utils::checked_with_lock}; /// type means the memory is backed by `netCDF` and should be /// freed using [`netcdf_sys::nc_free_vlen`] or [`netcdf_sys::nc_free_vlens`] /// to avoid memory leaks. -/// ```rust +/// ```rust,no_run /// # use netcdf::types::*; /// #[repr(C)] /// struct Vlen { @@ -113,7 +113,7 @@ use crate::{error::Result, utils::checked_with_lock}; /// ``` /// ## String type /// String types must be freed using [`netcdf_sys::nc_free_string`]. -/// ```rust +/// ```rust,no_run /// # use netcdf::types::*; /// #[repr(transparent)] /// struct NcString(*mut std::ffi::c_char); From ff3a91a05e6965cacc43521a14af1e8ba571253c Mon Sep 17 00:00:00 2001 From: Magnus Ulimoen Date: Wed, 19 Jun 2024 13:20:29 +0200 Subject: [PATCH 4/4] Remove derive as default feature --- .github/workflows/ci.yml | 14 +++++++------- README.md | 2 +- netcdf/Cargo.toml | 2 +- netcdf/src/lib.rs | 21 +++++++++++++++++++++ netcdf/tests/types.rs | 1 + 5 files changed, 31 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4801253..e29e1f8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,9 +37,9 @@ jobs: - name: Check formatting run: cargo fmt -- --check - name: Documentation - run: cargo doc --workspace + run: cargo doc --workspace --features netcdf/derive - name: Clippy - run: cargo clippy --workspace -- -D warnings + run: cargo clippy --features netcdf/derive --workspace -- -D warnings test_apt: name: test apt @@ -71,10 +71,10 @@ jobs: toolchain: ${{ matrix.rust }} - name: Build - run: cargo build --verbose --workspace --exclude netcdf-src + run: cargo build --verbose --features netcdf/derive --workspace --exclude netcdf-src - name: Test - run: cargo test --verbose --workspace --exclude netcdf-src --exclude netcdf-derive + run: cargo test --verbose --features netcdf/derive --workspace --exclude netcdf-src --exclude netcdf-derive conda: name: conda @@ -106,7 +106,7 @@ jobs: export HDF5_DIR="$CONDA_PREFIX" export NETCDF_DIR="$CONDA_PREFIX" [ "${{runner.os}}" != "Windows" ] && export RUSTFLAGS="-C link-args=-Wl,-rpath,$CONDA_PREFIX/lib" - cargo test -vv --workspace --exclude netcdf-src --exclude netcdf-derive + cargo test -vv --workspace --exclude netcdf-src --exclude netcdf-derive --features netcdf/derive static_builds: name: static builds @@ -131,7 +131,7 @@ jobs: uses: dtolnay/rust-toolchain@stable with: {toolchain: '${{matrix.rust}}'} - name: Build and test - run: cargo test -vv --features static --workspace --exclude netcdf-derive + run: cargo test -vv --features netcdf/derive,static --workspace --exclude netcdf-derive addr_san: name: Address sanitizer @@ -147,4 +147,4 @@ jobs: env: RUSTFLAGS: "-Z sanitizer=address" RUSTDOCFLAGS: "-Z sanitizer=address" - run: cargo test --features netcdf-sys/static --target x86_64-unknown-linux-gnu --workspace --exclude netcdf-derive + run: cargo test --features netcdf-sys/static,netcdf/derive --target x86_64-unknown-linux-gnu --workspace --exclude netcdf-derive diff --git a/README.md b/README.md index 2792dfb..19a77a0 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ Supported: * Open/Append/Create modes * Reading from memory * Unlimited dimensions -* User defined types (enum, compound, other types requires additional work) +* User defined types, using the feature `derive` (enum, compound, other types requires additional work) Not (yet) supported (PRs welcome): * Writing using memory-mapped file diff --git a/netcdf/Cargo.toml b/netcdf/Cargo.toml index cd63ddc..906f5c0 100644 --- a/netcdf/Cargo.toml +++ b/netcdf/Cargo.toml @@ -16,7 +16,7 @@ exclude = ["examples/**", "tests/**"] build = "build.rs" [features] -default = ["ndarray", "derive"] +default = ["ndarray"] static = ["netcdf-sys/static"] derive = ["dep:netcdf-derive"] diff --git a/netcdf/src/lib.rs b/netcdf/src/lib.rs index 0397232..b653d98 100644 --- a/netcdf/src/lib.rs +++ b/netcdf/src/lib.rs @@ -95,6 +95,27 @@ //! var.put((11.., ..), values.view())?; //! # Ok(()) } //! ``` +//! +//! How to derive `NcTypeDescriptor` for a custom type (requires `derive` feature flag). +//! See [NcTypeDescriptor] for additional examples. +//! +//! ```no_run +//! # #[cfg(not(feature = "derive"))] +//! # fn main() { /* This test does nothing without derive feature flag */ } +//! # #[cfg(feature = "derive")] +//! # fn main() -> Result<(), Box> { +//! #[repr(C)] +//! #[derive(netcdf::NcType)] +//! struct Foo { +//! bar: f64, +//! baz: i64, +//! } +//! let mut file = netcdf::create("custom.nc")?; +//! file.add_type::()?; +//! let mut var = file.add_variable::("variable", &[])?; +//! var.put_value(Foo { bar: 1.0, baz: 2 }, ())?; +//! # Ok(()) } +//! #![warn(missing_docs)] #![allow(clippy::must_use_candidate)] diff --git a/netcdf/tests/types.rs b/netcdf/tests/types.rs index b3d09bc..9f2c48b 100644 --- a/netcdf/tests/types.rs +++ b/netcdf/tests/types.rs @@ -454,6 +454,7 @@ fn char() { } #[test] +#[cfg(feature = "derive")] fn no_subtype() { #[derive(netcdf::NcType)] #[repr(C)]