A lightweight, high-performance Rust library providing common traits for implementing multiformats types with zero-copy decoding and flexible encoding strategies.
- 🚀 High Performance: Optimized varint encoding with minimal allocations
- 📦 Zero-Copy Decoding: Parse data without unnecessary copying
- 🎯 Type Safety: Validated newtypes for compile-time guarantees
- 🔧 Flexible Encoding: Three encoding strategies for different use cases
- 🌐 no_std Support: Works in embedded and constrained environments
- 🧵 Thread-Safe: All traits are
Send + Syncsafe - 📝 Well-Documented: Comprehensive documentation with examples
- ✅ Thoroughly Tested: 150+ tests including property-based and concurrency tests
Add this to your Cargo.toml:
[dependencies]
multitrait = "1.0"For no_std environments:
[dependencies]
multitrait = { version = "1.0", default-features = false }use multitrait::{EncodeInto, TryDecodeFrom};
// Encoding: Convert a value to compact varint bytes
let value = 42u32;
let encoded = value.encode_into();
println!("Encoded {} as {:?}", value, encoded);
// Decoding: Parse bytes back to original value
let (decoded, remaining) = u32::try_decode_from(&encoded).unwrap();
assert_eq!(decoded, value);
assert!(remaining.is_empty());Encode values into a compact varint Vec<u8>. Best for one-off encoding operations.
use multitrait::EncodeInto;
let value = 1000u16;
let bytes = value.encode_into(); // Allocates new Vec<u8>Zero-allocation encoding into an existing buffer. Best for encoding multiple values or hot paths.
use multitrait::EncodeIntoBuffer;
let mut buffer = Vec::with_capacity(100);
// Encode multiple values with minimal allocations
42u8.encode_into_buffer(&mut buffer);
1000u16.encode_into_buffer(&mut buffer);
100000u32.encode_into_buffer(&mut buffer);
println!("Encoded {} bytes total", buffer.len());Stack-based encoding for no_std environments. Returns a fixed-size array with the actual length.
use multitrait::EncodeIntoArray;
let (array, len) = 42u8.encode_into_array();
assert_eq!(&array[..len], &[42]);
// Maximum sizes known at compile time
assert_eq!(<u32 as EncodeIntoArray>::MAX_ENCODED_SIZE, 5);Fallibly decode values from byte slices with zero-copy semantics. Returns the decoded value and remaining unconsumed bytes.
use multitrait::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());
// Sequential decoding 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 null/sentinel values.
use multitrait::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 for types requiring validation.
use multitrait::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. Provides compile-time guarantees that bytes represent valid encodings.
use multitrait::EncodedBytes;
// Validation happens at construction
let valid = vec![42u8];
let encoded = EncodedBytes::try_from(valid).unwrap();
// Invalid data is rejected
let invalid = vec![0x80]; // Truncated varint
assert!(EncodedBytes::try_from(invalid).is_err());
// Type system ensures valid data
fn process_encoded(data: EncodedBytes) {
// No need to validate - type guarantees validity
println!("Processing {} bytes", data.len());
}All decode operations return a Result with a structured Error type:
use multitrait::{TryDecodeFrom, Error};
let truncated = vec![0xFF]; // Incomplete varint
match u16::try_decode_from(&truncated) {
Ok((value, _)) => println!("Decoded: {}", value),
Err(Error::UnsignedVarintDecode { source }) => {
eprintln!("Decode failed: {}", source);
}
Err(e) => eprintln!("Other error: {}", e),
}Error::UnsignedVarintDecode: Varint decoding failed (truncated data, invalid encoding, etc.)
All errors include source chains for debugging and support backtraces when the std feature is enabled.
Choose the right encoding strategy for your use case:
-
EncodeInto- Good for one-off encodings- Single allocation per call
- Simple API
- Use when encoding individual values
-
EncodeIntoBuffer- Best for multiple values- Zero allocations when buffer has capacity
- Reusable buffer
- Use in hot paths or when encoding multiple values
-
EncodeIntoArray- Best for embedded systems- Zero heap allocations (stack only)
- Deterministic performance
- Use in
no_stdor real-time systems
- Zero allocations: Returns slice references to existing data
- Zero copying: No data duplication during decode
- Constant-time validation: Efficient varint format checking
Varint encoding is highly efficient:
- Values 0-127: 1 byte
- Values 128-16,383: 2 bytes
- Values 16,384-2,097,151: 3 bytes
- And so on...
Maximum encoded sizes:
u8,bool: 2 bytes maxu16: 3 bytes maxu32: 5 bytes maxu64,usize(64-bit): 10 bytes maxu128: 19 bytes max
All traits and types in this crate are Send + Sync, making them safe to use in concurrent contexts.
All these patterns work safely:
use std::sync::Arc;
use std::thread;
use multitrait::{EncodeInto, TryDecodeFrom};
// Parallel encoding
let handles: Vec<_> = (0..10)
.map(|i| {
thread::spawn(move || {
let value = i as u32 * 100;
value.encode_into()
})
})
.collect();
// Shared read access
let data = Arc::new(vec![42u8, 100, 200]);
let handles: Vec<_> = (0..4)
.map(|_| {
let data = Arc::clone(&data);
thread::spawn(move || {
let (value, _) = u8::try_decode_from(&data).unwrap();
value
})
})
.collect();All operations are lock-free with no shared mutable state.
This crate works in no_std environments with alloc:
[dependencies]
multitrait = { version = "1.0", default-features = false }Use EncodeIntoArray for heap-free encoding in embedded systems:
#![no_std]
extern crate alloc;
use alloc::vec::Vec;
use multitrait::EncodeIntoArray;
// Stack-only encoding (no heap required for encoding)
let (array, len) = 42u8.encode_into_array();
// Only allocate if you need to store it
let vec = Vec::from(&array[..len]);std(default): Enables standard library support- Enables
std::error::Errorimplementation - Enables backtrace support in errors
- Disable for
no_std:default-features = false
- Enables
All traits are implemented for:
bool: Encoded as 0 (false) or 1 (true)u8,u16,u32,u64,u128: Variable-length encodingusize: Platform-dependent (32-bit or 64-bit)
See the examples/ directory for complete examples:
basic.rs- Basic encoding and decodingerror_handling.rs- Error handling patternscustom_type.rs- Implementing traits for custom typesno_std.rs- Using the crate in no_std environments
Run examples with:
cargo run --example basic
cargo run --example error_handling
cargo run --example custom_typeThe crate includes 150+ tests:
- Unit tests for all trait implementations
- Property-based tests with proptest
- Concurrency tests for thread safety
- Security tests for malicious inputs
- Edge case tests
Run tests with:
cargo testGenerate and view the full API documentation:
cargo doc --openLicensed under Apache-2.0. See LICENSE for details.
Contributions are welcome! Please ensure:
- All tests pass (
cargo test) - Code is formatted (
cargo fmt) - No clippy warnings (
cargo clippy) - New features include tests and documentation