Skip to content

Commit 3577bfa

Browse files
committed
Make common API for &str/DimensionIdentifier
1 parent 100da01 commit 3577bfa

File tree

10 files changed

+358
-195
lines changed

10 files changed

+358
-195
lines changed

netcdf/src/dimension.rs

Lines changed: 171 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,118 @@ use netcdf_sys::*;
77

88
use super::error;
99

10+
mod sealed {
11+
pub trait Sealed {}
12+
}
13+
14+
/// Types which can be used to distinguish dimensions
15+
/// in a netCDF file. This can be `&str` (the normal use case)
16+
/// or a special identifier when using nested groups.
17+
///
18+
/// This trait is not expected to be implemented elsewhere and is therefore sealed.
19+
///
20+
/// # Examples
21+
/// (helper function to show consumption of type)
22+
/// ```rust
23+
/// # use netcdf::AsNcDimensions;
24+
/// fn take(d: impl AsNcDimensions) {}
25+
/// ```
26+
/// Normally one uses the name of the dimension to specify the dimension
27+
/// ```rust
28+
/// # use netcdf::AsNcDimensions;
29+
/// # fn take(d: impl AsNcDimensions) {}
30+
/// take(()); // scalar
31+
/// take("x"); // single dimension
32+
/// take(["x", "y"]); // multiple dimensions
33+
/// ```
34+
/// When working with dimensions across groups, it might be necessary
35+
/// to use dimension identifiers to get the correct group dimension
36+
/// ```rust,no_run
37+
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
38+
/// # use netcdf::AsNcDimensions;
39+
/// # fn take(d: impl AsNcDimensions) {}
40+
/// let file = netcdf::open("test.nc")?;
41+
/// let dim = file.dimension("x").expect("File does not contain dimension");
42+
/// let dimid = dim.identifier();
43+
///
44+
/// take(dimid); // from a dimension identifier
45+
/// take([dimid, dimid]); // from multiple identifiers
46+
/// # Ok(()) }
47+
/// ```
48+
pub trait AsNcDimensions: sealed::Sealed {
49+
/// Convert from a slice of [`&str`]/[`DimensionIdentifier`] to concrete dimensions
50+
/// which are guaranteed to exist in this file
51+
fn get_dimensions<'g>(&self, ncid: nc_type) -> error::Result<Vec<Dimension<'g>>>;
52+
}
53+
54+
impl sealed::Sealed for &[&str] {}
55+
impl AsNcDimensions for &[&str] {
56+
fn get_dimensions<'g>(&self, ncid: nc_type) -> error::Result<Vec<Dimension<'g>>> {
57+
self.iter()
58+
.map(|&name| match dimension_from_name(ncid, name) {
59+
Ok(Some(x)) => Ok(x),
60+
Ok(None) => Err(format!("Dimension {name} not found").into()),
61+
Err(e) => Err(e),
62+
})
63+
.collect()
64+
}
65+
}
66+
impl<const N: usize> sealed::Sealed for [&str; N] {}
67+
impl<const N: usize> AsNcDimensions for [&str; N] {
68+
fn get_dimensions<'g>(&self, ncid: nc_type) -> error::Result<Vec<Dimension<'g>>> {
69+
self.as_slice().get_dimensions(ncid)
70+
}
71+
}
72+
impl<const N: usize> sealed::Sealed for &[&str; N] {}
73+
impl<const N: usize> AsNcDimensions for &[&str; N] {
74+
fn get_dimensions<'g>(&self, ncid: nc_type) -> error::Result<Vec<Dimension<'g>>> {
75+
self.as_slice().get_dimensions(ncid)
76+
}
77+
}
78+
79+
impl sealed::Sealed for &[DimensionIdentifier] {}
80+
impl AsNcDimensions for &[DimensionIdentifier] {
81+
fn get_dimensions<'g>(&self, ncid: nc_type) -> error::Result<Vec<Dimension<'g>>> {
82+
self.iter()
83+
.map(|dimid| match dimension_from_identifier(ncid, *dimid) {
84+
Ok(Some(x)) => Ok(x),
85+
Ok(None) => Err("Dimension id does not exist".into()),
86+
Err(e) => Err(e),
87+
})
88+
.collect()
89+
}
90+
}
91+
impl<const N: usize> sealed::Sealed for [DimensionIdentifier; N] {}
92+
impl<const N: usize> AsNcDimensions for [DimensionIdentifier; N] {
93+
fn get_dimensions<'g>(&self, ncid: nc_type) -> error::Result<Vec<Dimension<'g>>> {
94+
self.as_slice().get_dimensions(ncid)
95+
}
96+
}
97+
impl<const N: usize> sealed::Sealed for &[DimensionIdentifier; N] {}
98+
impl<const N: usize> AsNcDimensions for &[DimensionIdentifier; N] {
99+
fn get_dimensions<'g>(&self, ncid: nc_type) -> error::Result<Vec<Dimension<'g>>> {
100+
self.as_slice().get_dimensions(ncid)
101+
}
102+
}
103+
impl sealed::Sealed for () {}
104+
impl AsNcDimensions for () {
105+
fn get_dimensions<'g>(&self, _ncid: nc_type) -> error::Result<Vec<Dimension<'g>>> {
106+
Ok(Vec::new())
107+
}
108+
}
109+
impl sealed::Sealed for &str {}
110+
impl AsNcDimensions for &str {
111+
fn get_dimensions<'g>(&self, ncid: nc_type) -> error::Result<Vec<Dimension<'g>>> {
112+
([*self]).get_dimensions(ncid)
113+
}
114+
}
115+
impl sealed::Sealed for DimensionIdentifier {}
116+
impl AsNcDimensions for DimensionIdentifier {
117+
fn get_dimensions<'g>(&self, ncid: nc_type) -> error::Result<Vec<Dimension<'g>>> {
118+
([*self]).get_dimensions(ncid)
119+
}
120+
}
121+
10122
/// Represents a netcdf dimension
11123
#[derive(Debug, Clone)]
12124
pub struct Dimension<'g> {
@@ -25,9 +137,23 @@ pub struct DimensionIdentifier {
25137
pub(crate) dimid: nc_type,
26138
}
27139

140+
impl DimensionIdentifier {
141+
// Internal netcdf detail, the top 16 bits gives the corresponding
142+
// file handle. This to ensure dimensions are not added from another
143+
// file which is unrelated to self
144+
pub(crate) fn belongs_to(&self, ncid: nc_type) -> bool {
145+
self.ncid >> 16 == ncid >> 16
146+
}
147+
}
148+
28149
#[allow(clippy::len_without_is_empty)]
29150
impl<'g> Dimension<'g> {
30151
/// Get current length of this dimension
152+
///
153+
/// ## Note
154+
/// A dimension can be unlimited (growable) and changes size
155+
/// if putting values to a variable which uses this
156+
/// dimension
31157
pub fn len(&self) -> usize {
32158
if let Some(x) = self.len {
33159
x.get()
@@ -66,7 +192,10 @@ impl<'g> Dimension<'g> {
66192
}
67193

68194
/// Grabs the unique identifier for this dimension, which
69-
/// can be used in `add_variable_from_identifiers`
195+
/// can be used in [`add_variable`](crate::FileMut::add_variable).
196+
///
197+
/// This is useful when working with nested groups and need
198+
/// to distinguish between names at different levels.
70199
pub fn identifier(&self) -> DimensionIdentifier {
71200
self.id
72201
}
@@ -272,6 +401,47 @@ pub(crate) fn dimension_from_name<'f>(
272401
}))
273402
}
274403

404+
pub(crate) fn dimension_from_identifier<'f>(
405+
ncid: nc_type,
406+
dimid: DimensionIdentifier,
407+
) -> error::Result<Option<Dimension<'f>>> {
408+
if !dimid.belongs_to(ncid) {
409+
return Err(error::Error::WrongDataset);
410+
}
411+
let dimid = dimid.dimid;
412+
413+
let mut dimlen = 0;
414+
unsafe {
415+
error::checked(super::with_lock(|| nc_inq_dimlen(ncid, dimid, &mut dimlen))).unwrap();
416+
}
417+
if dimlen != 0 {
418+
// Have to check if this dimension is unlimited
419+
let mut nunlim = 0;
420+
unsafe {
421+
error::checked(super::with_lock(|| {
422+
nc_inq_unlimdims(ncid, &mut nunlim, std::ptr::null_mut())
423+
}))?;
424+
}
425+
if nunlim != 0 {
426+
let mut unlimdims = Vec::with_capacity(nunlim.try_into()?);
427+
unsafe {
428+
error::checked(super::with_lock(|| {
429+
nc_inq_unlimdims(ncid, std::ptr::null_mut(), unlimdims.as_mut_ptr())
430+
}))?;
431+
}
432+
unsafe { unlimdims.set_len(nunlim.try_into()?) }
433+
if unlimdims.contains(&dimid) {
434+
dimlen = 0;
435+
}
436+
}
437+
}
438+
Ok(Some(Dimension {
439+
len: core::num::NonZeroUsize::new(dimlen),
440+
id: super::dimension::DimensionIdentifier { ncid, dimid },
441+
_group: PhantomData,
442+
}))
443+
}
444+
275445
pub(crate) fn add_dimension_at<'f>(
276446
ncid: nc_type,
277447
name: &str,

netcdf/src/file.rs

Lines changed: 37 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use std::path;
77
use netcdf_sys::*;
88

99
use super::attribute::{Attribute, AttributeValue};
10-
use super::dimension::{self, Dimension};
10+
use super::dimension::{AsNcDimensions, Dimension};
1111
use super::error;
1212
use super::group::{Group, GroupMut};
1313
use super::variable::{NcPutGet, Variable, VariableMut};
@@ -338,20 +338,50 @@ impl FileMut {
338338
))
339339
}
340340

341-
/// Create a Variable into the dataset, with no data written into it
341+
/// Create a variable in the dataset, with no data written into it
342342
///
343-
/// Dimensions are identified using the name of the dimension, and will recurse upwards
344-
/// if not found in the current group.
345-
pub fn add_variable<'f, T>(
343+
/// # Examples
344+
/// ## Adding variables with different types
345+
/// ```rust,no_run
346+
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
347+
/// let mut file = netcdf::create("file.nc")?;
348+
/// file.add_dimension("x", 10)?;
349+
/// file.add_dimension("y", 11)?;
350+
/// file.add_variable::<u8, _>("var_u8", &["y", "x"])?;
351+
/// file.add_variable::<i8, _>("var_i8", &["y", "x"])?;
352+
/// file.add_variable::<u64, _>("var_u64", &["y", "x"])?;
353+
/// file.add_variable::<f64, _>("var_f64", &["y", "x"])?;
354+
/// # Ok(())}
355+
/// ```
356+
/// ## Adding a scalar variable
357+
/// ```rust,no_run
358+
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
359+
/// let mut file = netcdf::create("scalar.nc")?;
360+
/// file.add_variable::<u8, _>("var2", ())?;
361+
/// # Ok(())}
362+
/// ```
363+
/// ## Using dimension identifiers in place of names
364+
/// ```rust,no_run
365+
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
366+
/// let mut file = netcdf::create("dimids.nc")?;
367+
/// let dimx = file.dimension("x").unwrap().identifier();
368+
/// let dimy = file.dimension("y").unwrap().identifier();
369+
/// file.add_variable::<u8, _>("var2", &[dimy, dimx])?;
370+
/// # Ok(())}
371+
///```
372+
///
373+
/// See [`AsNcDimensions`] for how to specify dimensions.
374+
pub fn add_variable<'f, T, D>(
346375
&'f mut self,
347376
name: &str,
348-
dims: &[&str],
377+
dims: D,
349378
) -> error::Result<VariableMut<'f>>
350379
where
351380
T: NcPutGet,
381+
D: AsNcDimensions,
352382
{
353383
let (ncid, name) = super::group::get_parent_ncid_and_stem(self.ncid(), name)?;
354-
VariableMut::add_from_str(ncid, T::NCTYPE, name, dims)
384+
VariableMut::add_from_dimids(ncid, T::NCTYPE, name, dims.get_dimensions(ncid)?)
355385
}
356386

357387
/// Create a variable with the specified type
@@ -410,19 +440,6 @@ impl FileMut {
410440
let (ncid, name) = super::group::get_parent_ncid_and_stem(self.ncid(), name)?;
411441
VariableMut::add_from_str(ncid, NC_STRING, name, dims)
412442
}
413-
/// Adds a variable from a set of unique identifiers, recursing upwards
414-
/// from the current group if necessary.
415-
pub fn add_variable_from_identifiers<'f, T>(
416-
&'f mut self,
417-
name: &str,
418-
dims: &[dimension::DimensionIdentifier],
419-
) -> error::Result<VariableMut<'f>>
420-
where
421-
T: NcPutGet,
422-
{
423-
let (ncid, name) = super::group::get_parent_ncid_and_stem(self.ncid(), name)?;
424-
super::variable::add_variable_from_identifiers(ncid, name, dims, T::NCTYPE)
425-
}
426443
}
427444

428445
#[cfg(feature = "has-mmap")]

netcdf/src/group.rs

Lines changed: 10 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use std::marker::PhantomData;
66
use netcdf_sys::*;
77

88
use super::attribute::{Attribute, AttributeValue};
9-
use super::dimension::Dimension;
9+
use super::dimension::{AsNcDimensions, Dimension};
1010
use super::error;
1111
use super::variable::{NcPutGet, Variable, VariableMut};
1212

@@ -244,18 +244,23 @@ impl<'f> GroupMut<'f> {
244244
/// Create a Variable into the dataset, with no data written into it
245245
///
246246
/// Dimensions are identified using the name of the dimension, and will recurse upwards
247-
/// if not found in the current group.
248-
pub fn add_variable<'g, T>(
247+
/// if not found in the current group. If the name is shadowed one can get an
248+
/// [`DimensionIdentifier`](crate::DimensionIdentifier) to ensure the right dimension
249+
/// is used
250+
///
251+
/// See [`AsNcDimensions`] for how to specify dimensions.
252+
pub fn add_variable<'g, T, D>(
249253
&'g mut self,
250254
name: &str,
251-
dims: &[&str],
255+
dims: D,
252256
) -> error::Result<VariableMut<'g>>
253257
where
254258
T: NcPutGet,
255259
'f: 'g,
260+
D: AsNcDimensions,
256261
{
257262
let (ncid, name) = super::group::get_parent_ncid_and_stem(self.id(), name)?;
258-
VariableMut::add_from_str(ncid, T::NCTYPE, name, dims)
263+
VariableMut::add_from_dimids(ncid, T::NCTYPE, name, dims.get_dimensions(ncid)?)
259264
}
260265
/// Adds a variable with a basic type of string
261266
pub fn add_string_variable<'g>(
@@ -266,19 +271,6 @@ impl<'f> GroupMut<'f> {
266271
let (ncid, name) = super::group::get_parent_ncid_and_stem(self.id(), name)?;
267272
VariableMut::add_from_str(ncid, NC_STRING, name, dims)
268273
}
269-
/// Adds a variable from a set of unique identifiers, recursing upwards
270-
/// from the current group if necessary.
271-
pub fn add_variable_from_identifiers<'g, T>(
272-
&'g mut self,
273-
name: &str,
274-
dims: &[super::dimension::DimensionIdentifier],
275-
) -> error::Result<VariableMut<'g>>
276-
where
277-
T: NcPutGet,
278-
{
279-
let (ncid, name) = super::group::get_parent_ncid_and_stem(self.id(), name)?;
280-
super::variable::add_variable_from_identifiers(ncid, name, dims, T::NCTYPE)
281-
}
282274

283275
/// Create a variable with the specified type
284276
pub fn add_variable_with_type(

netcdf/src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@
7171
//! file.add_unlimited_dimension("time")?;
7272
//!
7373
//! // A variable can now be declared, and must be created from the dimension names.
74-
//! let mut var = file.add_variable::<i32>(
74+
//! let mut var = file.add_variable::<i32, _>(
7575
//! "crab_coolness_level",
7676
//! &["time", "ncrabs"],
7777
//! )?;
@@ -114,7 +114,7 @@ pub mod types;
114114
pub(crate) mod variable;
115115

116116
pub use attribute::{Attribute, AttributeValue};
117-
pub use dimension::{Dimension, DimensionIdentifier};
117+
pub use dimension::{AsNcDimensions, Dimension, DimensionIdentifier};
118118
pub use error::{Error, Result};
119119
pub use extent::{Extent, Extents};
120120
#[cfg(feature = "has-mmap")]

0 commit comments

Comments
 (0)