hdf5-pure
Pure-Rust HDF5 reader/writer. No C dependencies, no build scripts, WASM-compatible.
Features
- Write HDF5 files with datasets, groups, attributes, and nested hierarchies
- Read HDF5 files (v0/v1/v2/v3 superblocks, v1/v2 object headers, contiguous/chunked/compact storage)
- SWMR (single-writer / multiple-reader) append and refreshing read for 1-D unlimited datasets, interoperable with the reference C library and h5py
- No C dependencies — compiles to
wasm32-unknown-unknownwith--no-default-features - MATLAB v7.3 compatible — userblock support, fixed-length ASCII attributes, variable-length string arrays, object references
- Deflate, shuffle, and scale-offset (lossless integer / lossy float) compression
- Compound types, enumerations, array types
- Complex number datasets (as compound
{real, imag})
Quick start
Writing
use ;
let mut builder = new;
// Datasets
builder.create_dataset
.with_f64_data
.with_shape;
// Groups with nested datasets
let mut grp = builder.create_group;
grp.create_dataset.with_f32_data;
grp.set_attr;
builder.add_group;
// Attributes on the root group
builder.set_attr;
builder.write.unwrap;
Reading
use File;
let file = open.unwrap;
let ds = file.dataset.unwrap;
println!; // [3]
println!; // [22.5, 23.1, 21.8]
let attrs = file.root.attrs.unwrap;
println!; // Some(I64(2))
In-memory (WASM)
use FileBuilder;
let mut builder = new;
builder.create_dataset.with_f64_data;
let bytes: = builder.finish.unwrap; // no filesystem needed
SWMR (single writer, multiple readers)
A single process can append to an unlimited dataset in place while other processes read it concurrently. The writer appends chunks and flushes in dependency order so readers only ever observe a consistent prefix; readers re-read to pick up new data. This interoperates with the reference HDF5 C library and h5py in both directions.
The dataset must have one unlimited dimension and be chunked (it is indexed by an Extensible Array, which the latest format selects automatically). Create it the usual way:
use FileBuilder;
let mut builder = new;
builder.create_dataset
.with_i32_data // initial rows
.with_shape
.with_maxshape // one unlimited dimension
.with_chunks;
builder.write.unwrap;
Append in place (each call flushes durably; the file stays valid for concurrent readers throughout):
use SwmrWriter;
let mut writer = open.unwrap;
writer.append_i32.unwrap;
writer.append_i32.unwrap;
writer.close.unwrap; // clears the SWMR flag; or just drop the writer
Follow a growing file from another process (or the reference C library / h5py writing in SWMR mode):
use File;
let mut file = open_swmr.unwrap;
let n = file.dataset.unwrap.shape.unwrap;
// ... later, after the writer appends ...
file.refresh.unwrap; // re-read appended data
let ds = file.dataset.unwrap;
println!;
Supported subset: one unlimited dimension, chunked, unfiltered (no compression on the appended dataset), chunk-aligned appends, no userblock. Growth is unbounded. SWMR requires the std filesystem (not the in-memory/WASM path). If a writer process exits without close(), the file is left marked as having an active SWMR writer; recover it with SwmrWriter::clear_swmr_flag(path) (the equivalent of h5clear).
Supported data types
Datasets
| Method | HDF5 type |
|---|---|
with_f64_data |
IEEE 64-bit float |
with_f32_data |
IEEE 32-bit float |
with_i8_data / with_i16_data / with_i32_data / with_i64_data |
Signed integers |
with_u8_data / with_u16_data / with_u32_data / with_u64_data |
Unsigned integers |
with_complex32_data |
Compound {real: f32, imag: f32} |
with_complex64_data |
Compound {real: f64, imag: f64} |
with_compound_data |
Arbitrary compound types |
with_enum_i32_data / with_enum_u8_data |
Enumeration types |
with_array_data |
Fixed-size array types |
with_path_references |
Object references (resolved by path) |
with_dtype + with_shape |
Empty/zero-dimension datasets |
Attributes
| Variant | HDF5 encoding |
|---|---|
AttrValue::F64 / F64Array |
64-bit float scalar/array |
AttrValue::I32 / I64 / I64Array |
Signed integer scalar/array |
AttrValue::U32 / U64 |
Unsigned integer scalar |
AttrValue::String / StringArray |
UTF-8 null-padded string |
AttrValue::AsciiString |
Fixed-length ASCII string |
AttrValue::VarLenAsciiArray |
Variable-length ASCII string array (global heap) |
Compression
// Deflate (zlib)
builder.create_dataset
.with_f64_data
.with_chunks
.with_deflate;
// Shuffle + deflate
builder.create_dataset
.with_f64_data
.with_chunks
.with_shuffle
.with_deflate;
Scale-offset (HDF5 filter id 6)
Scale-offset stores each chunk's values as offsets from the chunk minimum, packed into the fewest bits the chunk's range needs. It is a built-in HDF5 filter, so files we write are readable by the reference C library, h5py, and MATLAB, and files those tools produce are readable by us.
use ScaleOffset;
// Integer mode is lossless. `0` lets the encoder pick the bit width per chunk.
builder.create_dataset
.with_i32_data
.with_chunks
.with_scale_offset;
// Float D-scale is lossy: values are rounded to N decimal digits before packing.
builder.create_dataset
.with_f64_data
.with_chunks
.with_scale_offset // keep 3 decimal digits
.with_deflate; // may be followed by deflate
| Mode | Datatype | Loss |
|---|---|---|
ScaleOffset::Integer(minbits) |
signed/unsigned integers | lossless |
ScaleOffset::FloatDScale(decimals) |
f32 / f64 |
lossy to decimals digits |
ZFP (optional, zfp feature)
Pure-Rust fixed-rate port of the LLNL/zfp codec, registered HDF5 filter
ID 32013. Byte-for-byte interoperable with the reference H5Z-ZFP plugin:
files we write are readable by h5py + hdf5plugin, and files those tools
produce are readable by us. Supported slice:
- Scalar types:
f32,f64,i32,i64 - Ranks: 1D, 2D, 3D, 4D (per-block sizes 4, 16, 64, 256)
- Mode: fixed-rate (
ratebits per value)
// Compile with `--features zfp`
builder.create_dataset
.with_f32_data
.with_shape
.with_chunks
.with_zfp; // 16 bits per value
Interop is enforced by tests/zfp_crosscheck.rs, which compares against
fixtures produced by h5py + hdf5plugin. See tests/fixtures/zfp/regen.py
for the generator — run it after any codec change.
Userblock (MATLAB v7.3)
let mut builder = new;
builder.with_userblock;
builder.create_dataset.with_f64_data;
let mut bytes = builder.finish.unwrap;
// Write MATLAB header into userblock
bytes = b'I';
bytes = b'M';
MATLAB struct pattern
use ;
let mut builder = new;
let mut grp = builder.create_group;
let mut fields = Vecnew;
for in
grp.set_attr;
grp.set_attr;
builder.add_group;
MATLAB v7.3 .mat via serde
With the serde feature, Rust structs can be serialized directly to .mat
v7.3 files and back:
use ;
use ;
let e = Experiment ;
to_file.unwrap;
let back: Experiment = from_file.unwrap;
assert_eq!;
The top-level value must be a struct (or HashMap<String, _>); each field
becomes a MATLAB variable. Mapping:
| Rust | HDF5 / MATLAB encoding |
|---|---|
f64, f32, i*, u* |
scalar dataset [1,1], MATLAB_class = "double" / "single" / "int*" / "uint*" |
bool |
uint8 scalar, MATLAB_class = "logical" |
String / &str |
uint16 [1, N] UTF-16LE, MATLAB_class = "char" |
Vec<T> of numeric T |
[1, N] row vector |
Matrix<T> or Vec<Vec<T>> of same length |
column-major 2-D dataset, HDF5 shape [cols, rows] |
Complex32 / Complex64 |
compound {real, imag} dataset |
| nested struct | HDF5 group with MATLAB_class = "struct", MATLAB_fields |
Option<T> (struct field) |
omitted if None |
| unit enum variant | UTF-16 char dataset holding the variant name |
Vec<Struct> / Vec<Option<T>> / ragged Vec<Vec<T>> |
cell array (MATLAB_class = "cell", object references into #refs#); None slots become struct([]) |
Cell array pattern
Sequences that don't unify into a numeric matrix lower to a MATLAB cell array. Each element is interned under the conventional #refs# group and the parent dataset stores object references.
use mat;
use ;
In MATLAB this loads as iscell(path) == true, path{1}.x, etc. Empty None slots load as struct([]) (isempty(fieldnames(...))).
Reader compatibility. Cell arrays load correctly in MATLAB, libmatio (reference C library), Julia's MAT.jl, and Python via pymatreader / hdf5storage. GNU Octave 11's load does not yet follow object references for v7.3 cells (warns "unknown datatype"); load such files with one of the above instead.
Not supported in this release: non-unit enum variants, MATLAB objects (classdef), datetime / categorical types.
Cargo features
| Feature | Default | Description |
|---|---|---|
std |
yes | File I/O, high-level reader API |
checksum |
yes | Jenkins hash for v2+ object headers |
deflate |
yes | Deflate compression (pure Rust backend) |
serde |
no | Serialize/deserialize MATLAB v7.3 .mat files via serde |
fast-checksum |
no | Hardware-accelerated CRC32 via crc32fast |
fast-deflate |
no | zlib-ng backend for deflate via flate2/zlib-ng |
mmap |
no | Memory-mapped file reading via memmap2 |
parallel |
no | Parallel chunk processing via rayon |
provenance |
no | SHA-256 data provenance tracking |
zfp |
no | ZFP fixed-rate compression (HDF5 filter 32013), f32/f64/i32/i64 × 1D–4D |
For WASM, disable default features:
[]
= { = "0.7", = false, = ["checksum"] }
Acknowledgements
The HDF5 format parsing and low-level I/O modules are derived from rustyhdf5 by the RustyStack project (MIT licensed).
License
MIT