rust_hdf5/attribute.rs
1//! Attribute support.
2//!
3//! Attributes are small metadata items attached to datasets (or groups).
4//! They are created via the [`AttrBuilder`] API obtained from
5//! [`H5Dataset::new_attr`](crate::dataset::H5Dataset::new_attr).
6//!
7//! # Example
8//!
9//! ```no_run
10//! use rust_hdf5::H5File;
11//! use rust_hdf5::types::VarLenUnicode;
12//!
13//! let file = H5File::create("attrs.h5").unwrap();
14//! let ds = file.new_dataset::<f32>().shape(&[10]).create("data").unwrap();
15//! let attr = ds.new_attr::<VarLenUnicode>().shape(()).create("units").unwrap();
16//! attr.write_scalar(&VarLenUnicode("meters".to_string())).unwrap();
17//! ```
18
19use std::marker::PhantomData;
20
21use crate::format::messages::attribute::AttributeMessage;
22
23use crate::error::{Hdf5Error, Result};
24use crate::file::{borrow_inner_mut, clone_inner, H5FileInner, SharedInner};
25use crate::types::VarLenUnicode;
26
27/// A handle to an HDF5 attribute.
28///
29/// After creating an attribute via [`AttrBuilder::create`], use
30/// [`write_scalar`](Self::write_scalar) or [`write_string`](Self::write_string)
31/// to set its value.
32///
33/// In read mode, use [`read_string`](Self::read_string) to read string attributes.
34pub struct H5Attribute {
35 file_inner: SharedInner,
36 ds_index: usize,
37 name: String,
38 /// The decoded attribute message for read-mode handles (carries the
39 /// datatype, needed to resolve variable-length string values).
40 read_attr: Option<AttributeMessage>,
41}
42
43impl H5Attribute {
44 /// Create a read-mode attribute handle from a decoded attribute message.
45 pub(crate) fn new_reader(file_inner: SharedInner, attr_msg: AttributeMessage) -> Self {
46 Self {
47 file_inner,
48 ds_index: usize::MAX,
49 name: attr_msg.name.clone(),
50 read_attr: Some(attr_msg),
51 }
52 }
53
54 /// Return the attribute name.
55 pub fn name(&self) -> &str {
56 &self.name
57 }
58
59 /// Write a scalar value to the attribute.
60 ///
61 /// For `VarLenUnicode`, this writes a fixed-length string attribute
62 /// whose size is determined by the string value.
63 pub fn write_scalar(&self, value: &VarLenUnicode) -> Result<()> {
64 let attr_msg = AttributeMessage::scalar_string(&self.name, &value.0);
65
66 let mut inner = borrow_inner_mut(&self.file_inner);
67 match &mut *inner {
68 H5FileInner::Writer(writer) => {
69 writer.add_dataset_attribute(self.ds_index, attr_msg)?;
70 Ok(())
71 }
72 H5FileInner::Reader(_) => Err(Hdf5Error::InvalidState(
73 "cannot write attributes in read mode".into(),
74 )),
75 H5FileInner::Closed => Err(Hdf5Error::InvalidState("file is closed".into())),
76 }
77 }
78
79 /// Write a string value to the attribute (convenience method).
80 pub fn write_string(&self, value: &str) -> Result<()> {
81 self.write_scalar(&VarLenUnicode(value.to_string()))
82 }
83
84 /// Write a numeric scalar attribute.
85 ///
86 /// ```no_run
87 /// # use rust_hdf5::H5File;
88 /// let file = H5File::create("num_attr.h5").unwrap();
89 /// let ds = file.new_dataset::<f32>().shape(&[10]).create("data").unwrap();
90 /// ds.write_raw(&[0.0f32; 10]).unwrap();
91 /// let attr = ds.new_attr::<f64>().shape(()).create("scale").unwrap();
92 /// attr.write_numeric(&3.14f64).unwrap();
93 /// ```
94 pub fn write_numeric<T: crate::types::H5Type>(&self, value: &T) -> Result<()> {
95 let es = T::element_size();
96 let raw = unsafe { std::slice::from_raw_parts(value as *const T as *const u8, es) };
97 let attr_msg = AttributeMessage::scalar_numeric(&self.name, T::hdf5_type(), raw.to_vec());
98
99 let mut inner = borrow_inner_mut(&self.file_inner);
100 match &mut *inner {
101 H5FileInner::Writer(writer) => {
102 writer.add_dataset_attribute(self.ds_index, attr_msg)?;
103 Ok(())
104 }
105 H5FileInner::Reader(_) => Err(Hdf5Error::InvalidState(
106 "cannot write attributes in read mode".into(),
107 )),
108 H5FileInner::Closed => Err(Hdf5Error::InvalidState("file is closed".into())),
109 }
110 }
111
112 /// Read a numeric scalar attribute.
113 ///
114 /// ```no_run
115 /// # use rust_hdf5::H5File;
116 /// let file = H5File::open("num_attr.h5").unwrap();
117 /// let ds = file.dataset("data").unwrap();
118 /// let attr = ds.attr("scale").unwrap();
119 /// let val: f64 = attr.read_numeric().unwrap();
120 /// ```
121 pub fn read_numeric<T: crate::types::H5Type>(&self) -> Result<T> {
122 let data = self
123 .read_attr
124 .as_ref()
125 .map(|a| &a.data)
126 .ok_or_else(|| Hdf5Error::InvalidState("attribute has no read data".into()))?;
127 let es = T::element_size();
128 if data.len() < es {
129 return Err(Hdf5Error::TypeMismatch(format!(
130 "attribute data {} bytes, need {} for type",
131 data.len(),
132 es
133 )));
134 }
135 unsafe {
136 let mut val = std::mem::MaybeUninit::<T>::uninit();
137 std::ptr::copy_nonoverlapping(data.as_ptr(), val.as_mut_ptr() as *mut u8, es);
138 Ok(val.assume_init())
139 }
140 }
141
142 /// Read the attribute value as a string.
143 ///
144 /// Handles both fixed-length string attributes and variable-length
145 /// string attributes (h5py's default), resolving a vlen value through
146 /// the global heap.
147 pub fn read_string(&self) -> Result<String> {
148 let attr = self.read_attr.as_ref().ok_or_else(|| {
149 Hdf5Error::InvalidState("attribute has no read data (write-mode handle?)".into())
150 })?;
151 let mut inner = borrow_inner_mut(&self.file_inner);
152 match &mut *inner {
153 H5FileInner::Reader(reader) => Ok(reader.attr_string_value(attr)?),
154 _ => {
155 // No reader available — fall back to the raw fixed-length
156 // interpretation.
157 let end = attr
158 .data
159 .iter()
160 .position(|&b| b == 0)
161 .unwrap_or(attr.data.len());
162 Ok(String::from_utf8_lossy(&attr.data[..end]).to_string())
163 }
164 }
165 }
166
167 /// Read the raw attribute data bytes.
168 pub fn read_raw(&self) -> Result<Vec<u8>> {
169 self.read_attr
170 .as_ref()
171 .map(|a| a.data.clone())
172 .ok_or_else(|| {
173 Hdf5Error::InvalidState("attribute has no read data (write-mode handle?)".into())
174 })
175 }
176}
177
178/// A fluent builder for creating attributes on datasets.
179///
180/// Obtained from [`H5Dataset::new_attr::<T>()`](crate::dataset::H5Dataset::new_attr).
181pub struct AttrBuilder<'a, T> {
182 file_inner: &'a SharedInner,
183 ds_index: usize,
184 _shape_set: bool,
185 _marker: PhantomData<T>,
186}
187
188impl<'a, T> AttrBuilder<'a, T> {
189 pub(crate) fn new(file_inner: &'a SharedInner, ds_index: usize) -> Self {
190 Self {
191 file_inner,
192 ds_index,
193 _shape_set: false,
194 _marker: PhantomData,
195 }
196 }
197
198 /// Set the attribute shape. Use `()` for a scalar attribute.
199 #[must_use]
200 pub fn shape<S>(mut self, _shape: S) -> Self {
201 // For now we only support scalar attributes.
202 self._shape_set = true;
203 self
204 }
205
206 /// Create the attribute with the given name.
207 ///
208 /// The attribute is created but does not yet have a value.
209 /// Call [`H5Attribute::write_scalar`] to set the value.
210 pub fn create(self, name: &str) -> Result<H5Attribute> {
211 Ok(H5Attribute {
212 file_inner: clone_inner(self.file_inner),
213 ds_index: self.ds_index,
214 name: name.to_string(),
215 read_attr: None,
216 })
217 }
218}