Skip to main content

hdf5_pure/
element.rs

1//! Generic, type-parameterized dataset I/O.
2//!
3//! The [`H5Element`] trait is the element bound that lets you read and write
4//! datasets generically over the scalar type, instead of reaching for a
5//! type-specific method like [`DatasetBuilder::with_i64_data`] or
6//! [`Dataset::read_i64`](crate::Dataset::read_i64). It powers two entry points:
7//!
8//! * [`DatasetBuilder::with_data`] — write a flat slice of any supported scalar,
9//!   inferring the datatype and shape from the slice.
10//! * [`Dataset::read`](crate::Dataset::read) — read a dataset back into a
11//!   `Vec<T>` for any supported scalar `T`.
12//!
13//! Both dispatch to the existing per-type methods, so a generic read or write
14//! has exactly the same datatype, endianness, and conversion behavior as the
15//! corresponding `with_*_data` / `read_*` call.
16//!
17//! # Example
18//!
19//! ```
20//! use hdf5_pure::{Dataset, Error, FileBuilder, H5Element};
21//!
22//! // A function generic over the element type — not possible with the
23//! // type-specific `with_*_data` / `read_*` methods.
24//! fn round_trip<T: H5Element + PartialEq + std::fmt::Debug>(name: &str, values: &[T]) {
25//!     let mut fb = FileBuilder::new();
26//!     fb.create_dataset(name).with_data(values);
27//!     let bytes = fb.finish().unwrap();
28//!
29//!     let file = hdf5_pure::File::from_bytes(bytes).unwrap();
30//!     let back: Vec<T> = file.dataset(name).unwrap().read().unwrap();
31//!     assert_eq!(values, back.as_slice());
32//! }
33//!
34//! round_trip("ints", &[1i64, 2, 3]);
35//! round_trip("floats", &[1.0f32, 2.5, -3.0]);
36//! ```
37
38use crate::error::Error;
39use crate::reader::Dataset;
40use crate::type_builders::DatasetBuilder;
41
42mod sealed {
43    pub trait Sealed {}
44}
45
46/// A Rust scalar type that can be stored as an HDF5 dataset element.
47///
48/// This trait is sealed: it is implemented for the fixed set of scalar types
49/// the crate's reader and writer support (`f32`, `f64`, the signed integers
50/// `i8`/`i16`/`i32`/`i64`, and the unsigned integers `u8`/`u16`/`u32`/`u64`)
51/// and cannot be implemented for other types. It is the element bound for the
52/// generic [`DatasetBuilder::with_data`] / [`Dataset::read`](crate::Dataset::read)
53/// helpers (and, with the `ndarray` feature, the
54/// [`with_ndarray`](crate::FileBuilder)/[`read_array`](crate::Dataset::read_array)
55/// family), and dispatches to the existing per-type reader/writer methods, so a
56/// generic read or write has exactly the same datatype, endianness, and
57/// conversion behavior as the corresponding
58/// [`Dataset::read_f64`](crate::Dataset::read_f64) /
59/// [`DatasetBuilder::with_f64_data`] call.
60pub trait H5Element: sealed::Sealed + Copy {
61    /// Read every element of `ds` as `Self`, in row-major order.
62    #[doc(hidden)]
63    fn read_from(ds: &Dataset<'_>) -> Result<Vec<Self>, Error>;
64
65    /// Set `builder`'s data and datatype from a flat row-major slice.
66    #[doc(hidden)]
67    fn write_into(builder: &mut DatasetBuilder, data: &[Self]);
68}
69
70macro_rules! impl_h5_element {
71    ($ty:ty, $read:ident, $write:ident) => {
72        impl sealed::Sealed for $ty {}
73        impl H5Element for $ty {
74            fn read_from(ds: &Dataset<'_>) -> Result<Vec<Self>, Error> {
75                ds.$read()
76            }
77            fn write_into(builder: &mut DatasetBuilder, data: &[Self]) {
78                builder.$write(data);
79            }
80        }
81    };
82}
83
84impl_h5_element!(f32, read_f32, with_f32_data);
85impl_h5_element!(f64, read_f64, with_f64_data);
86impl_h5_element!(i8, read_i8, with_i8_data);
87impl_h5_element!(i16, read_i16, with_i16_data);
88impl_h5_element!(i32, read_i32, with_i32_data);
89impl_h5_element!(i64, read_i64, with_i64_data);
90impl_h5_element!(u8, read_u8, with_u8_data);
91impl_h5_element!(u16, read_u16, with_u16_data);
92impl_h5_element!(u32, read_u32, with_u32_data);
93impl_h5_element!(u64, read_u64, with_u64_data);
94
95impl DatasetBuilder {
96    /// Set the dataset's data and datatype from a flat slice of any supported
97    /// scalar type.
98    ///
99    /// This is the generic counterpart of the type-specific `with_*_data`
100    /// methods (e.g. [`with_f64_data`](Self::with_f64_data)): it infers the
101    /// datatype from `T` and, unless [`with_shape`](Self::with_shape) has
102    /// already set one, takes the shape to be the 1-D `[data.len()]`. The
103    /// builder is returned so chunking, compression, and attributes can be
104    /// chained.
105    ///
106    /// Because the element type is a generic parameter, you can write code that
107    /// is generic over the stored type:
108    ///
109    /// ```
110    /// use hdf5_pure::{FileBuilder, H5Element};
111    ///
112    /// fn store<T: H5Element>(fb: &mut FileBuilder, name: &str, values: &[T]) {
113    ///     fb.create_dataset(name).with_data(values);
114    /// }
115    /// ```
116    ///
117    /// For N-dimensional arrays, see
118    /// [`with_ndarray`](crate::FileBuilder) (the `ndarray` feature).
119    pub fn with_data<T: H5Element>(&mut self, data: &[T]) -> &mut Self {
120        T::write_into(self, data);
121        self
122    }
123}
124
125impl Dataset<'_> {
126    /// Read the dataset into a `Vec<T>` for any supported scalar type, in
127    /// row-major order.
128    ///
129    /// This is the generic counterpart of the type-specific `read_*` methods
130    /// (e.g. [`read_f64`](Self::read_f64)). The element type is usually inferred
131    /// from the binding, so a call site reads as
132    /// `let v: Vec<f64> = ds.read()?;`, or you can name it with turbofish:
133    /// `ds.read::<i32>()?`.
134    ///
135    /// `T` is the type you want the elements *delivered as*, not an assertion
136    /// about the dataset's stored type. The stored bytes are coerced into `T`
137    /// using the same rules as [`read_f64`](Self::read_f64) and its siblings, so
138    /// the conversion can be lossy: reading an `f64` dataset as `i32` truncates,
139    /// and reading an `i32` dataset as `f64` widens. There is no check that `T`
140    /// matches the on-disk datatype, so pick `T` to match the stored type when
141    /// you need an exact, lossless read.
142    ///
143    /// # Errors
144    ///
145    /// Propagates any error from the underlying typed read (see
146    /// [`read_f64`](Self::read_f64)).
147    pub fn read<T: H5Element>(&self) -> Result<Vec<T>, Error> {
148        T::read_from(self)
149    }
150}