Built-in types
- Boolean (one-bit alignment):
bool - Discrete numbers:
- Signed (one-byte alignment):
i8,i16,i32,i64,i128 - Unsigned (one-byte alignment):
u8,u16,u32,u64,u128 - Unsigned (four-bit alignment):
u4 - Signed and unsigned (one-bit alignment):
iNanduN(U1,U2,U3, ...U64,I2...I64)
- Signed (one-byte alignment):
- Nibble-based variable length u32:
UNib32(1 to 11 nibbles) - Floating point numbers:
f32,f64 - Textual:
- UTF-8 string
String - TODO: With max bounded length
- UTF-8 string
- Sequences:
- Arrays:
- Arbitrary length array:
Vec<T> - Byte array:
Vec<u8> - Arbitrary length array (no alloc):
RefVec<'i, T> - Byte array (no alloc):
RefVec<'i, u8> - TODO: Max bounded
- TODO: Fixed length array:
[T; N]
- Arbitrary length array:
- Arrays:
Option<T>andResult<T, E>RefBox<T>for self-referential types.-
User-defined:
- Struct
- Enum with or without data variants
-
Not yet supported or not decided whether to support:
- Tuple
- Unicode character:
char(4B) - ASCII character
c_char(1B) (ASCII) and string:c_str - Map
Library types
There are a lot more types as a part of a standard library (date, time, version, numbers, SI units, etc.). See the overview.
Self-referential types
Self-referential types are supported through the RefBox<T>, providing similar semantics to Rust Box<T> type, but
without using heap allocation on no_std.
Alignment
Some types are one- or four-bit aligned and the rest are one-byte aligned. Dense packing is used to save space, including in enum discriminants (which can even be U1). Byte arrays, strings, and Unsized objects are all one-byte aligned to limit code complexity and computations required. Unused bits are set to zero and can be reclaimed when evolving a type.
For example:
fn version1() {
let mut buf = [0u8; 8];
let mut wr = BufWriter::new(&mut buf[..]);
wr.write_bool(true).unwrap();
wr.write_u8(0xAA).unwrap();
let bytes = wr.finish_and_take().unwrap();
assert_eq!(bytes, &[0x80, 0xAA]);
}
In a future version while older one is still in use, it was decided to add some more data:
fn version1_1() {
let mut buf = [0u8; 8];
let mut wr = BufWriter::new(&mut buf[..]);
wr.write_bool(true).unwrap();
wr.write(&Some(U6::new(5).unwrap())).unwrap();
wr.write_u8(0xAA).unwrap();
let bytes = wr.finish_and_take().unwrap();
assert_eq!(bytes, &[0xC5, 0xAA]);
}
Older code can still read new data and will skip the Option, and newer code can read old data, yielding None. All the while, the serialized size didn't even change (it could have though, it's just an example).
TODO: Subtypes (bounded numbers and array lengths)
Simple checked numbers where only a range of values is allowed:
u16<{1..=512}>
Set of allowed values:
u8<{0..=8}, 12, 16, 20, 24, 32, 48, 64>
Numbers are checked before serialization and after deserialization.
TODO: SI support
Specify SI unit for any number:
- current:
f32<"A"> - velocity:
f32<"m/s">
Units are not transmitted over the wire, used as a hint for code generation and in UI tool.