macro_rules! array {
($($t:tt)*) => { ... };
}Expand description
Declare a struct’s schema as a positional array, for every format.
The bracket counterpart of object!. Fields are encoded in declaration
order with no keys at all: JSON writes them between [ and ], BEVE
behind a generic-array header.
#[derive(Default, PartialEq, Debug)]
struct Vec3 {
x: f64,
y: f64,
z: f64,
}
structio::array!(Vec3 [x, y, z]);
let v = Vec3 { x: 1.0, y: 2.0, z: 3.0 };
assert_eq!(structio::to_string(&v), "[1,2,3]");
assert_eq!(structio::from_str::<Vec3>("[1,2,3]").unwrap(), v);The list holds field names and nothing else. object!’s #[required]
marker has no counterpart here and is not accepted: an element is required
by its position, and an array of the wrong length is refused under every
policy.
Generics work as they do for object!: in brackets before the type, with
'de written out when the type borrows from the input.
#[derive(Default)]
struct Labelled<'a, T> {
label: &'a str,
value: T,
}
structio::array!(['de, T: structio::ReadWrite + Default] Labelled<'de, T> [label, value]);§Homogeneous structs
When every field is the same type, name it in front of the field list, the way an array type names its element:
#[derive(Default, PartialEq, Debug)]
struct Rgb {
r: u8,
g: u8,
b: u8,
}
structio::array!(Rgb [u8; r, g, b]);JSON is unchanged by this. What it buys is BEVE, which stores a run of one
type as a typed array: one header for the whole run instead of one per
element, and the values as a contiguous block. Rgb goes out in five bytes
rather than eight, three f64s in twenty-six rather than twenty-nine, and
three bools in three rather than five, since booleans pack one per bit.
The bytes are exactly what a [u8; 3] of the same values would have
produced, which is also what another implementation writes for its own
three-component colour.
The element type is checked: every field has to be it, and a mismatch is a
compile error at the declaration. It also has to be Copy, because the
payload is one contiguous run and a struct’s fields are not required to be
laid out as one, so they are gathered into a block first. That bound holds
whether or not the type turns out to have a typed array: one that does not,
such as another struct, falls back to a generic array and is written
exactly as it would have been without the element type.
Reading is unchanged either way: an array-declared struct takes a generic array or a typed one whatever it was declared as, so adding an element type changes what you write without narrowing what you accept.
Like object!, a declaration has to name every field, and .. at the
end of the list says an omission is deliberate. It goes behind the element
type where there is one.
#[derive(Default)]
struct Rgb { r: u8, g: u8, b: u8, label: String }
structio::array!(Rgb [u8; r, g, b, ..]);
let c = Rgb { r: 1, g: 2, b: 3, label: "red".into() };
assert_eq!(structio::to_string(&c), "[1,2,3]");§When to reach for it
Position is cheaper than a key in every respect: nothing is hashed, nothing
is compared, no KeyMap is built or stored, and the keys
themselves are off the wire. For a type whose field names carry no
information anyway, a coordinate or a colour or a row of a table, that is
most of the per-value cost gone.
What it costs is room to move. An object can tolerate a field appearing or
disappearing, since a reader matches on names and can be asked to step over
a key it does not know
(SkipUnknown; the default refuses it). An array
cannot, under any policy: adding, removing, or reordering a field silently
changes what every position means, and lengthens or shortens the array,
which readers of the old shape reject. Declare a type this way when its
shape is fixed by something outside your control, and object! otherwise.
§What it expands to
One Elements impl carrying the field count, and then,
for each format, four small impls: ReadArray and WriteArray for the
per-element dispatch, and Read/Write delegating to the array forms.
There is no key list and no hash, because there is nothing to look up.
An element type adds the typed-array header and payload writer to BEVE’s
WriteArray, both of which fold away when it is absent.
A tuple is the same encoding without the names, and goes through the same
drivers, so (f64, f64, f64) and the Vec3 above produce identical bytes
in both formats.