Common traits for multiformats types in Rust. The crate gives encoding,
decoding, and null value traits with zero-copy decoding and no_std support.
- Varint encoding with minimal allocations.
- Zero-copy decoding.
TryDecodeFromreturns the remaining bytes. - Zero-allocation encoding.
EncodeIntoBufferreuses an existing buffer. - Stack-based encoding.
EncodeIntoArrayforno_stdand embedded systems. no_stdsupport withalloc.- Validated newtype.
EncodedBytesgives type-level guarantees. #![deny(unsafe_code)]set at the crate root.- All types are
Send + Sync. - 155 tests: unit, property-based, security, concurrency, and round-trip.
Add this to your Cargo.toml:
[dependencies]
multi-trait = "1.1"For no_std environments:
[dependencies]
multi-trait = { version = "1.1", default-features = false }MSRV: Rust 1.85 (Edition 2024).
use multi_trait::{EncodeInto, TryDecodeFrom};
// Encode a value to varint bytes
let value = 42u32;
let encoded = value.encode_into();
println!("Encoded {} as {:?}", value, encoded);
// Decode the bytes back to the value
let (decoded, remaining) = u32::try_decode_from(&encoded).unwrap();
assert_eq!(decoded, value);
assert!(remaining.is_empty());Encode a value into a varint Vec<u8>. Use this for one-off encoding.
use multi_trait::EncodeInto;
let value = 1000u16;
let bytes = value.encode_into(); // Allocates a new Vec<u8>Encode values into an existing buffer with no allocation. Use this in hot paths or when you encode multiple values.
use multi_trait::EncodeIntoBuffer;
let mut buffer = Vec::with_capacity(100);
42u8.encode_into_buffer(&mut buffer);
1000u16.encode_into_buffer(&mut buffer);
100_000u32.encode_into_buffer(&mut buffer);
println!("Encoded {} bytes total", buffer.len());Encode a value into a stack-allocated array. Use this in no_std or
real-time systems.
use multi_trait::EncodeIntoArray;
let (array, len) = 42u8.encode_into_array();
assert_eq!(&array[..len], &[42]);
assert_eq!(<u32 as EncodeIntoArray>::MAX_ENCODED_SIZE, 5);Decode a value from a byte slice. Returns the value and the remaining bytes. No allocation occurs.
use multi_trait::TryDecodeFrom;
let bytes = vec![0xFF, 0xFF, 0x03]; // Varint encoding of 65535
let (value, remaining) = u16::try_decode_from(&bytes).unwrap();
assert_eq!(value, 65535);
assert!(remaining.is_empty());
// Decode multiple values from one buffer
let bytes = vec![0x01, 0x02, 0x03];
let (first, rest) = u8::try_decode_from(&bytes).unwrap();
let (second, rest) = u8::try_decode_from(rest).unwrap();
let (third, rest) = u8::try_decode_from(rest).unwrap();
assert_eq!((first, second, third), (1, 2, 3));Define and check for a null or sentinel value.
use multi_trait::Null;
struct MyId(u64);
impl Null for MyId {
fn null() -> Self {
MyId(0)
}
fn is_null(&self) -> bool {
self.0 == 0
}
}
let null_id = MyId::null();
assert!(null_id.is_null());
let valid_id = MyId(12345);
assert!(!valid_id.is_null());Fallible version of Null. Use it for types that need validation.
use multi_trait::TryNull;
struct ValidatedId(u64);
impl TryNull for ValidatedId {
type Error = &'static str;
fn try_null() -> Result<Self, Self::Error> {
Ok(ValidatedId(0))
}
fn is_null(&self) -> bool {
self.0 == 0
}
}A validated newtype for varint-encoded byte sequences. Construction checks that the bytes are a valid encoding.
use multi_trait::EncodedBytes;
let valid = vec![42u8];
let encoded = EncodedBytes::try_from(valid).unwrap();
let invalid = vec![0x80]; // Truncated varint
assert!(EncodedBytes::try_from(invalid).is_err());
fn process_encoded(data: EncodedBytes) {
// The type guarantees the data is valid
println!("Processing {} bytes", data.len());
}All decode operations return Result with a structured Error type:
use multi_trait::{TryDecodeFrom, Error};
let truncated = vec![0xFF]; // Incomplete varint
match u16::try_decode_from(&truncated) {
Ok((value, _)) => println!("Decoded: {}", value),
Err(Error::UnsignedVarintDecode { .. }) => {
eprintln!("Decode failed");
}
Err(e) => eprintln!("Other error: {}", e),
}The Error enum is #[non_exhaustive]. It has these variants:
UnsignedVarintDecode: Varint decoding failed. The cause can be truncated data or an invalid encoding.InsufficientData: The input slice does not have enough bytes to decode the requested type.InvalidEncoding: The data is structurally invalid. This variant is for future use and custom validation.
All errors give source chains for debugging. Backtraces are available when
the std feature is on.
Pick the encoding strategy for your use case:
EncodeInto— One allocation per call. Use this for one-off encodings.EncodeIntoBuffer— Zero allocations when the buffer has capacity. Use this in hot paths or when you encode multiple values.EncodeIntoArray— Zero heap allocations. Use this inno_stdor real-time systems.
- Zero allocations. Returns slice references to the input data.
- No data copy during decode.
- Efficient varint format checking.
Varint encoding uses 1 to 10 bytes for integers. The size depends on the value.
- Values 0 to 127: 1 byte.
- Values 128 to 16,383: 2 bytes.
- Values 16,384 to 2,097,151: 3 bytes.
Maximum encoded sizes by type:
u8,bool: 2 bytes.u16: 3 bytes.u32: 5 bytes.u64,usize(64-bit): 10 bytes.u128: 19 bytes.
All traits and types in this crate are Send + Sync. You can use them in
concurrent contexts.
All operations are lock-free. No mutable state is shared.
The crate works in no_std environments with alloc:
[dependencies]
multi-trait = { version = "1.1", default-features = false }Use EncodeIntoArray for heap-free encoding in embedded systems:
#![no_std]
extern crate alloc;
use alloc::vec::Vec;
use multi_trait::EncodeIntoArray;
let (array, len) = 42u8.encode_into_array();
let vec = Vec::from(&array[..len]);std(default). Enables standard library support. It enablesstd::error::Errorimplementation and backtrace support in errors. Disable it forno_stdwithdefault-features = false. The crate needsallocwhenstdis off.
All traits are implemented for:
bool: Encoded as 0 (false) or 1 (true).u8,u16,u32,u64,u128: Variable-length varint encoding.usize: Platform-dependent (32-bit or 64-bit).[u8; N]: Fixed-length byte arrays.EncodeIntoencodes the raw bytes without a varint prefix.TryDecodeFromreads exactly N bytes. Use this for BLS share identifiers and other fixed-size binary data.
EncodeIntoBuffer and EncodeIntoArray are implemented for bool,
u8, u16, u32, u64, u128, and usize. They do not support
[u8; N].
See the examples/ directory for complete examples:
basic.rs— Basic encoding and decoding.error_handling.rs— Error handling patterns.custom_type.rs— Implement the traits for custom types.no_std.rs— Use the crate inno_stdenvironments.
Run an example:
cargo run --example basicThe crate has 155 tests: unit, property-based, concurrency, security, and edge case tests.
Run all tests:
cargo testGenerate and view the API documentation:
cargo doc --openLicensed under Apache-2.0. See LICENSE for details.
Contributions are welcome. Before you submit a change, make sure:
- All tests pass (
cargo test). - The code is formatted (
cargo fmt). - No clippy warnings (
cargo clippy). - New features include tests and documentation.