OSAL-RS-Serde
An extensible, lightweight serialization/deserialization framework for Rust, inspired by Serde but optimized for embedded systems and no-std environments.
Features
- ✅ No-std compatible: Works perfectly in bare-metal embedded environments
- ✅ Memory-efficient: Optimized for resource-constrained systems with predictable memory usage
- ✅ Extensible: Easy to create custom serializers for any format (binary, JSON, MessagePack, etc.)
- ✅ Derive Macro: Full support for
#[derive(Serialize, Deserialize)] - ✅ Type-safe: Leverages Rust's type system for compile-time guarantees
- ✅ Zero-copy: Direct buffer reads/writes without intermediate allocations
- ✅ Reusable: Can be used in any project, not just with osal-rs
Supported Types
Primitives
- Integers:
u8,i8,u16,i16,u32,i32,u64,i64,u128,i128 - Floats:
f32,f64 - Boolean:
bool
Compound Types
- Arrays:
[T; N]for any serializable type T - Tuples:
(T1, T2),(T1, T2, T3)up to 3 elements - Option:
Option<T>for optional fields
Collections (with alloc feature)
- Vec:
Vec<T>for dynamic arrays - String:
Stringand&str
Custom Types
- Any struct with
#[derive(Serialize, Deserialize)] - Nested struct composition fully supported
Binary Format & Memory Sizes
The default ByteSerializer uses little-endian binary format with no padding:
bool: 1 byte (0 or 1)
u8/i8: 1 byte
u16/i16: 2 bytes
u32/i32: 4 bytes
u64/i64: 8 bytes
u128/i128: 16 bytes
f32: 4 bytes (IEEE 754)
f64: 8 bytes (IEEE 754)
Option<T>: 1 byte tag + sizeof(T) if Some, 1 byte if None
Array[T;N]: sizeof(T) * N (no length prefix)
Tuple: sum(sizeof each field)
Vec<T>: 4 bytes (u32 length) + sizeof(T) * length
String: 4 bytes (u32 length) + UTF-8 bytes
Installation
Add to your Cargo.toml:
[]
= { = "0.3", = ["derive"] }
Available features:
default: Includesallocfeature for Vec and String supportalloc: Enables dynamic allocation support (Vec, String)std: Enables standard library support (error traits, etc.)derive: Enables#[derive(Serialize, Deserialize)]macros (recommended)
For no-std environments without allocation:
[]
= { = "0.3", = false, = ["derive"] }
Project Structure
The osal-rs-serde workspace includes:
- osal-rs-serde: Core library with traits and implementations
- osal-rs-serde/derive: Procedural macros for automatic derivation (optional, enabled via
derivefeature)
Everything is contained in a single package for ease of use.
Usage
With Derive Macros (Recommended)
Basic Struct Example
use ;
Struct with Optional Fields
use ;
Struct with Arrays and Tuples
use ;
Nested Structs
use ;
Complex Embedded System Example
use ;
Manual Implementation
use ;
Usage with OSAL-RS Queue
Perfect for inter-task communication in RTOS environments:
use ;
use ;
Supported Types
The framework automatically supports serialization/deserialization for:
- Primitives:
bool,u8,i8,u16,i16,u32,i32,u64,i64,u128,i128,f32,f64 - Compound: Arrays
[T; N], tuples(T1, T2)and(T1, T2, T3),Option<T> - Collections (with
alloc):Vec<T>,String,&str - Custom: Any struct implementing
Serialize/Deserialize(or using derive) - Nested: Full support for nested struct composition
Custom Serializers
You can create custom serializers to support different formats (JSON, MessagePack, CBOR, etc.):
use ;
// Similarly implement Deserializer trait for deserialization
See examples/custom_serializer.rs for a complete text-based serializer implementation.
Performance Considerations
Buffer Size Calculation
For fixed-size types, calculate buffer size at compile time:
// Total: 7 bytes
const BUFFER_SIZE: usize = 7;
let mut buffer = ;
Zero-Copy Operation
The serializer writes directly to your buffer with no intermediate allocations:
// Stack-allocated buffer - no heap allocation
let mut buffer = ;
let len = to_bytes?;
// Use only the filled portion
send_to_uart;
Compile-Time Guarantees
The type system ensures correctness:
- Cannot deserialize wrong type from buffer
- Compile-time checks for trait implementations
- No runtime type checks needed
Examples
The examples/ directory contains complete working examples demonstrating various features:
Running Examples
# Basic struct serialization
# Using derive macros (recommended approach)
# Arrays and tuples
# Nested struct composition
# Optional fields with Option<T>
# Complex embedded system (robot control)
# Custom serializer implementation
# Integration with OSAL-RS
Example Descriptions
basic.rs: Simple manual implementation without derive macroswith_derive.rs: Same example using#[derive]macrosarrays_tuples.rs: Working with arrays and tuples in structsnested_structs.rs: Nested struct composition patternsoptional_fields.rs: UsingOption<T>for optional datarobot_control.rs: Complex real-world embedded system example with motor controlcustom_serializer.rs: Creating a custom text-based serializerintegration.rs: Integration with OSAL-RS queues for inter-task communication
Best Practices
1. Use Derive Macros
Always prefer derive macros for standard serialization:
2. Calculate Buffer Sizes
Pre-calculate buffer sizes for better performance:
const
let mut buffer = ;
3. Error Handling
Always handle serialization errors appropriately:
match to_bytes
4. Versioning
Consider adding version fields for forward compatibility:
Comparison with Serde
| Feature | osal-rs-serde | serde |
|---|---|---|
| No-std support | ✅ Native | ✅ Via feature |
| Derive macros | ✅ Built-in | ✅ Separate crate |
| Binary size | Very small | Medium/Large |
| Supported formats | Custom (extendable) | Many built-in |
| Target use case | Embedded/RTOS | General purpose |
| Zero-copy | ✅ Always | Depends on format |
| Compile time | Fast | Slower |
| Learning curve | Gentle | Moderate |
Choose osal-rs-serde when:
- Working in embedded/no-std environments
- Need predictable memory usage
- Want minimal binary size
- Require simple, fast compilation
- Building RTOS applications
Choose serde when:
- Need many pre-built format implementations
- Working primarily with std
- Require advanced features (flatten, rename, etc.)
- Ecosystem integration is important
License
GPL-3.0 - See LICENSE for details.
Author
Antonio Salsi passy.linux@zresa.it