| Crates.io | osal-rs-serde |
| lib.rs | osal-rs-serde |
| version | 0.3.2 |
| created_at | 2026-01-21 10:39:56.36634+00 |
| updated_at | 2026-01-23 16:10:29.26689+00 |
| description | Serialization/Deserialization framework for osal-rs - extensible and reusable |
| homepage | https://github.com/HiHappyGarden/osal-rs |
| repository | https://github.com/HiHappyGarden/osal-rs |
| max_upload_size | |
| id | 2058846 |
| size | 117,808 |
An extensible, lightweight serialization/deserialization framework for Rust, inspired by Serde but optimized for embedded systems and no-std environments.
✅ 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
u8, i8, u16, i16, u32, i32, u64, i64, u128, i128f32, f64bool[T; N] for any serializable type T(T1, T2), (T1, T2, T3) up to 3 elementsOption<T> for optional fieldsalloc feature)Vec<T> for dynamic arraysString and &str#[derive(Serialize, Deserialize)]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
Add to your Cargo.toml:
[dependencies]
osal-rs-serde = { version = "0.3", features = ["derive"] }
Available features:
default: Includes alloc feature 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:
[dependencies]
osal-rs-serde = { version = "0.3", default-features = false, features = ["derive"] }
The osal-rs-serde workspace includes:
derive feature)Everything is contained in a single package for ease of use.
use osal_rs_serde::{Serialize, Deserialize, to_bytes, from_bytes};
#[derive(Serialize, Deserialize)]
struct SensorData {
temperature: i16,
humidity: u8,
pressure: u32,
}
fn main() {
// Create a structure
let data = SensorData {
temperature: 25,
humidity: 60,
pressure: 1013,
};
// Serialize
let mut buffer = [0u8; 32];
let len = to_bytes(&data, &mut buffer).unwrap();
println!("Serialized {} bytes", len);
// Deserialize
let read_data: SensorData = from_bytes(&buffer[..len]).unwrap();
println!("Temperature: {}", read_data.temperature);
}
use osal_rs_serde::{Serialize, Deserialize, to_bytes, from_bytes};
#[derive(Serialize, Deserialize)]
struct Config {
device_id: u32,
name: Option<u8>, // Optional device name code
enabled: bool,
timeout: Option<u16>, // Optional timeout in ms
}
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();
}
use osal_rs_serde::{Serialize, Deserialize, to_bytes, from_bytes};
#[derive(Serialize, Deserialize)]
struct TelemetryPacket {
timestamp: u64,
coordinates: (i32, i32, i32), // x, y, z
samples: [u16; 8], // 8 sensor readings
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);
}
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);
}
use osal_rs_serde::{Serialize, Deserialize, to_bytes, from_bytes};
#[derive(Serialize, Deserialize)]
struct MotorControl {
motor_id: u8,
speed: i16, // -1000 to 1000
direction: bool, // true = forward, false = reverse
current: u16, // mA
}
#[derive(Serialize, Deserialize)]
struct RobotState {
timestamp: u64,
motors: [MotorControl; 4], // 4 motors
battery_voltage: u16, // mV
temperature: i8, // °C
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, // 12.4V
temperature: 35,
error_flags: 0,
};
let mut buffer = [0u8; 256];
let len = to_bytes(&state, &mut buffer).unwrap();
println!("Robot state serialized: {} bytes", len);
// Deserialize and check
let decoded: RobotState = from_bytes(&buffer[..len]).unwrap();
println!("Battery: {}mV, Temp: {}°C",
decoded.battery_voltage,
decoded.temperature);
}
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()?,
})
}
}
Perfect for inter-task communication in RTOS environments:
use osal_rs::os::{Queue, QueueFn};
use osal_rs_serde::{Serialize, Deserialize, to_bytes, from_bytes};
#[derive(Serialize, Deserialize)]
struct Command {
id: u32,
action: u8,
params: [u16; 4],
}
fn sender_task(queue: &Queue) {
let cmd = Command {
id: 42,
action: 0x10,
params: [100, 200, 300, 400],
};
let mut buffer = [0u8; 32];
let len = to_bytes(&cmd, &mut buffer).unwrap();
queue.post(&buffer[..len], 100).unwrap();
}
fn receiver_task(queue: &Queue) {
let mut buffer = [0u8; 32];
queue.fetch(&mut buffer, 100).unwrap();
let cmd: Command = from_bytes(&buffer).unwrap();
println!("Received command: id={}, action=0x{:02X}", cmd.id, cmd.action);
}
The framework automatically supports serialization/deserialization for:
bool, u8, i8, u16, i16, u32, i32, u64, i64, u128, i128, f32, f64[T; N], tuples (T1, T2) and (T1, T2, T3), Option<T>alloc): Vec<T>, String, &strSerialize/Deserialize (or using derive)You can create custom serializers to support different formats (JSON, MessagePack, CBOR, etc.):
use osal_rs_serde::{Serializer, Deserializer, Error};
struct JsonSerializer<'a> {
buffer: &'a mut [u8],
position: usize,
}
impl<'a> Serializer for JsonSerializer<'a> {
type Error = Error;
fn serialize_u32(&mut self, name: &str, v: u32) -> Result<(), Self::Error> {
// Write JSON format: "name": value
// Your implementation here...
Ok(())
}
fn serialize_bool(&mut self, name: &str, v: bool) -> Result<(), Self::Error> {
// Write JSON format: "name": true/false
// Your implementation here...
Ok(())
}
// Implement other serialize_* methods...
}
// Similarly implement Deserializer trait for deserialization
See examples/custom_serializer.rs for a complete text-based serializer implementation.
For fixed-size types, calculate buffer size at compile time:
#[derive(Serialize, Deserialize)]
struct Packet {
id: u32, // 4 bytes
value: i16, // 2 bytes
flags: u8, // 1 byte
}
// Total: 7 bytes
const BUFFER_SIZE: usize = 7;
let mut buffer = [0u8; BUFFER_SIZE];
The serializer writes directly to your buffer with no intermediate allocations:
// Stack-allocated buffer - no heap allocation
let mut buffer = [0u8; 64];
let len = to_bytes(&data, &mut buffer)?;
// Use only the filled portion
send_to_uart(&buffer[..len]);
The type system ensures correctness:
The examples/ directory contains complete working examples demonstrating various features:
# Basic struct serialization
cargo run --example basic --features derive
# Using derive macros (recommended approach)
cargo run --example with_derive --features derive
# Arrays and tuples
cargo run --example arrays_tuples --features derive
# Nested struct composition
cargo run --example nested_structs --features derive
# Optional fields with Option<T>
cargo run --example optional_fields --features derive
# Complex embedded system (robot control)
cargo run --example robot_control --features derive
# Custom serializer implementation
cargo run --example custom_serializer
# Integration with OSAL-RS
cargo run --example integration --features derive
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: Using Option<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 communicationAlways prefer derive macros for standard serialization:
#[derive(Serialize, Deserialize)]
struct MyStruct {
// fields...
}
Pre-calculate buffer sizes for better performance:
const fn calculate_size() -> usize {
size_of::<u32>() + size_of::<i16>() + size_of::<bool>()
}
let mut buffer = [0u8; calculate_size()];
Always handle serialization errors appropriately:
match to_bytes(&data, &mut buffer) {
Ok(len) => send_data(&buffer[..len]),
Err(Error::BufferTooSmall) => {
// Handle buffer overflow
}
Err(e) => {
// Handle other errors
}
}
Consider adding version fields for forward compatibility:
#[derive(Serialize, Deserialize)]
struct Message {
version: u8,
// other fields...
}
| 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:
Choose serde when:
GPL-3.0 - See LICENSE for details.
Antonio Salsi passy.linux@zresa.it