Serialization
Cerulion Core provides automatic serialization for anyCopy type, eliminating the need to manually define protobuf messages for simple structs. This page explains how serialization works and when to use it.
Automatic Serialization
The SerializableMessage Trait
All message types in Cerulion Core must implement theSerializableMessage trait:
The Rust trait is automatically implemented for
Copy types. In Python and C++, you implement to_bytes() and from_bytes() methods directly on your message classes/structs.Blanket Implementation for Copy Types
Cerulion Core automatically implementsSerializableMessage for any Copy type:
This means you can use simple structs like
SensorData without writing any serialization code. Just make sure your struct implements Copy and has #[repr(C)] layout.Using Copy Types
Simple Example
Here’s a simple struct that works automatically:The
#[repr(C)] attribute ensures the struct has a C-compatible memory layout, which is required for zero-copy serialization. This also enables future cross-language compatibility.How It Works
- For Local Communication: Data is sent as raw bytes using zero-copy shared memory (iceoryx2)
- For Network Communication: Data is serialized to bytes and sent over Zenoh
Supported Types
Primitive Types
All primitive types are supported:Fixed-Size Arrays
Fixed-size arrays of primitives work automatically:Fixed-Size Strings
Use fixed-size byte arrays for strings (notString):
Nested Structs
NestedCopy structs work as long as the outer struct is #[repr(C)]:
Safety Considerations
Safe Types
Raw byte serialization using#[repr(C)] is safe for:
- ✅ Primitive types (
u8,u16,u32,u64,i8,i16,i32,i64,f32,f64,bool) - ✅ Arrays of primitive types (
[f32; 10]) - ✅ Structs containing only primitives (with
#[repr(C)]) - ✅ Fixed-size byte arrays (
[u8; N])
Unsafe Types
Avoid using raw serialization for:- ❌ Types with pointers or references
- ❌ Types with padding-dependent layouts (use
#[repr(C)]to fix) - ❌
String(use[u8; N]instead) - ❌
Vec,HashMap, or other heap-allocated collections - ❌ Types with
Dropimplementations that manage resources
Optional Protobuf Support
If you need cross-language compatibility or schema evolution, you can use protobuf:Protobuf is useful for:
- Cross-language communication (Rust ↔ Python ↔ C++)
- Schema evolution (adding/removing fields)
- Complex nested message types
- When you need guaranteed compatibility
Serialization Flow
Local Transport (Zero-Copy)
No serialization occurs - data is copied byte-for-byte to shared memory.Network Transport (Serialization)
Serialization occurs - struct is converted to bytes using#[repr(C)] layout.
Type Size and Layout
Checking Type Size
You can check the size of your message type:Memory Layout
With#[repr(C)], the memory layout is guaranteed:
Visual Memory Layout:
Memory Layout Table:
| Field | Type | Size | Offset | Byte Range |
|---|---|---|---|---|
timestamp | u64 | 8 bytes | 0 | 0-7 |
temperature | f32 | 4 bytes | 8 | 8-11 |
pressure | f32 | 4 bytes | 12 | 12-15 |
- Fields are stored contiguously in memory (no padding)
- Total size: 16 bytes (8 + 4 + 4)
- Memory layout matches C struct layout exactly
- Each field starts immediately after the previous one
The
#[repr(C)] attribute ensures the struct has the same memory layout as a C struct. This is essential for:- Zero-copy serialization (local transport)
- Cross-language compatibility (future Python/C++ support)
- Predictable binary format
Examples
Example 1: Simple Sensor Data
Example 2: Fixed-Size Arrays
Example 3: Fixed-Size Strings
Best Practices
1. Always Use #[repr(C)]
2. Use Fixed-Size Arrays
3. Use Fixed-Size Byte Arrays for Strings
4. Keep Types Small
Large structs increase serialization time and memory usage:Troubleshooting
”Type doesn’t implement Copy”
Problem: Your struct doesn’t implementCopy.
Solution: Ensure all fields are Copy types:
“Serialization failed”
Problem: Type has incompatible layout. Solution: Add#[repr(C)]: