OSAL-RS-Serde
An extensible 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 environments
- ✅ Memory-efficient: Optimized for resource-constrained systems
- ✅ Extensible: Easy to create custom serializers for any format
- ✅ Derive Macro: Support for
#[derive(Serialize, Deserialize)]
- ✅ Type-safe: Leverages Rust's type system
- ✅ 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
- Arrays:
[T; N]
- Tuples:
(T1, T2), (T1, T2, T3)
- Option:
Option<T>
Custom
- Any struct with
#[derive(Serialize, Deserialize)]
Memory Sizes
bool: 1 byte
u8/i8: 1 byte
u16/i16: 2 bytes
u32/i32: 4 bytes
u64/i64: 8 bytes
u128/i128: 16 bytes
f32: 4 bytes
f64: 8 bytes
Option<T>: 1 byte (tag) + sizeof(T) if Some, 1 byte if None
Array[T;N]: sizeof(T) * N
Tuple: sum(sizeof each field)
Installation
Add to your Cargo.toml:
[dependencies]
osal-rs-serde = { version = "0.3", features = ["derive"] }
Available features:
alloc: Enables dynamic allocation support (included in default)
std: Enables standard library support
derive: Enables #[derive(Serialize, Deserialize)] macros
Project Structure
The osal-rs-serde crate includes:
- Core library: Traits and implementations for serialization/deserialization
- Derive macros (optional): Procedural macros for automatic derivation (in
derive/)
Everything is contained in a single package for ease of use.
Usage
With Derive Macros (Recommended)
Basic Struct Example
use osal_rs_serde::{Serialize, Deserialize, to_bytes, from_bytes};
#[derive(Serialize, Deserialize)]
struct SensorData {
temperature: i16,
humidity: u8,
pressure: u32,
}
fn main() {
let data = SensorData {
temperature: 25,
humidity: 60,
pressure: 1013,
};
let mut buffer = [0u8; 32];
let len = to_bytes(&data, &mut buffer).unwrap();
println!("Serialized {} bytes", len);
let read_data: SensorData = from_bytes(&buffer[..len]).unwrap();
println!("Temperature: {}", read_data.temperature);
}
Struct with Optional Fields
use osal_rs_serde::{Serialize, Deserialize, to_bytes, from_bytes};
#[derive(Serialize, Deserialize)]
struct Config {
device_id: u32,
name: Option<u8>, enabled: bool,
timeout: Option<u16>, }
fn main() {
let config = Config {
device_id: 100,
name: Some(42),
enabled: true,
timeout: None,
};
let mut buffer = [0u8; 64];
let len = to_bytes(&config, &mut buffer).unwrap();
let decoded: Config = from_bytes(&buffer[..len]).unwrap();
}
Struct with Arrays and Tuples
use osal_rs_serde::{Serialize, Deserialize, to_bytes, from_bytes};
#[derive(Serialize, Deserialize)]
struct TelemetryPacket {
timestamp: u64,
coordinates: (i32, i32, i32), samples: [u16; 8], status: u8,
}
fn main() {
let packet = TelemetryPacket {
timestamp: 1642857600,
coordinates: (100, 200, 50),
samples: [10, 20, 30, 40, 50, 60, 70, 80],
status: 0xFF,
};
let mut buffer = [0u8; 128];
let len = to_bytes(&packet, &mut buffer).unwrap();
println!("Telemetry packet: {} bytes", len);
}
Nested Structs
use osal_rs_serde::{Serialize, Deserialize, to_bytes, from_bytes};
#[derive(Serialize, Deserialize)]
struct Location {
latitude: i32,
longitude: i32,
}
#[derive(Serialize, Deserialize)]
struct Device {
id: u32,
battery: u8,
location: Location,
active: bool,
}
fn main() {
let device = Device {
id: 42,
battery: 85,
location: Location {
latitude: 45500000,
longitude: 9200000,
},
active: true,
};
let mut buffer = [0u8; 64];
let len = to_bytes(&device, &mut buffer).unwrap();
let decoded: Device = from_bytes(&buffer[..len]).unwrap();
println!("Device at {}, {}",
decoded.location.latitude,
decoded.location.longitude);
}
Complex Embedded System Example
use osal_rs_serde::{Serialize, Deserialize, to_bytes, from_bytes};
#[derive(Serialize, Deserialize)]
struct MotorControl {
motor_id: u8,
speed: i16, direction: bool, current: u16, }
#[derive(Serialize, Deserialize)]
struct RobotState {
timestamp: u64,
motors: [MotorControl; 4], battery_voltage: u16, temperature: i8, error_flags: u32,
}
fn main() {
let state = RobotState {
timestamp: 1000000,
motors: [
MotorControl { motor_id: 0, speed: 500, direction: true, current: 1200 },
MotorControl { motor_id: 1, speed: 500, direction: true, current: 1150 },
MotorControl { motor_id: 2, speed: -300, direction: false, current: 800 },
MotorControl { motor_id: 3, speed: -300, direction: false, current: 850 },
],
battery_voltage: 12400, temperature: 35,
error_flags: 0,
};
let mut buffer = [0u8; 256];
let len = to_bytes(&state, &mut buffer).unwrap();
println!("Robot state serialized: {} bytes", len);
let decoded: RobotState = from_bytes(&buffer[..len]).unwrap();
println!("Battery: {}mV, Temp: {}°C",
decoded.battery_voltage,
decoded.temperature);
}
Manual Implementation
use osal_rs_serde::{Serialize, Deserialize, Serializer, Deserializer};
struct Point {
x: i32,
y: i32,
}
impl Serialize for Point {
fn serialize<S: Serializer>(&self, serializer: &mut S) -> Result<(), S::Error> {
serializer.serialize_i32(self.x)?;
serializer.serialize_i32(self.y)?;
Ok(())
}
}
impl Deserialize for Point {
fn deserialize<D: Deserializer>(deserializer: &mut D) -> Result<Self, D::Error> {
Ok(Point {
x: deserializer.deserialize_i32()?,
y: deserializer.deserialize_i32()?,
})
}
}
Usage with OSAL-RS Queue
use osal_rs::os::{Queue, QueueFn};
use osal_rs_serde::{Serialize, Deserialize, to_bytes, from_bytes};
#[derive(Serialize, Deserialize)]
struct Message {
id: u32,
value: i16,
}
fn main() {
let queue = Queue::new(10, 32).unwrap();
let msg = Message { id: 42, value: 100 };
let mut buffer = [0u8; 32];
let len = to_bytes(&msg, &mut buffer).unwrap();
queue.post(&buffer[..len], 100).unwrap();
let mut recv_buffer = [0u8; 32];
queue.fetch(&mut recv_buffer, 100).unwrap();
let received: Message = from_bytes(&recv_buffer).unwrap();
}
Supported Types
The framework automatically supports:
- Primitive types:
bool, u8, i8, u16, i16, u32, i32, u64, i64, u128, i128
- Floating point:
f32, f64
- Arrays:
[T; N]
- Tuples:
(T1, T2), (T1, T2, T3)
- Option:
Option<T>
- Any custom type that implements
Serialize/Deserialize
Custom Serializers
You can create custom serializers to support different formats:
use osal_rs_serde::{Serializer, Error};
struct JsonSerializer {
}
impl Serializer for JsonSerializer {
type Error = Error;
fn serialize_u32(&mut self, v: u32) -> Result<(), Self::Error> {
Ok(())
}
}
Examples
See the examples/ folder for complete examples:
cargo run --example basic
cargo run --example with_derive --features derive
cargo run --example with_queue
Comparison with Serde
| Feature |
osal-rs-serde |
serde |
| No-std |
✅ |
✅ |
| Derive macro |
✅ |
✅ |
| Binary size |
Small |
Medium/Large |
| Supported formats |
Customizable |
Many built-in |
| Target |
Embedded/RTOS |
General purpose |
License
GPL-3.0 - See LICENSE for details.
Author
Antonio Salsi passy.linux@zresa.it