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}