Skip to main content

Overview

The serialization system provides three main components:
  1. SerializableMessage trait - Main trait for all serializable message types
  2. ProtoSerializable trait - Optional protobuf serialization support
  3. raw module - Low-level functions for Copy type serialization
For a quick start guide with examples, see the Quickstart page.

Quick Reference

ComponentDescriptionCategory
SerializableMessageMain serialization traitCore
to_bytes()Serialize message to bytesCore
from_bytes()Deserialize message from bytes (with relocatable support)Core
ProtoSerializableProtobuf serialization traitProtobuf
to_proto_bytes()Serialize protobuf messageProtobuf
from_proto_bytes()Deserialize protobuf messageProtobuf
raw::struct_to_bytes()Convert Copy type to raw bytesRaw
raw::bytes_to_struct()Convert raw bytes to Copy typeRaw

SerializableMessage Trait

Signature

pub trait SerializableMessage: Send + Sync + 'static {
    fn to_bytes(&self) -> Result<Vec<u8>, Box<dyn Error>>;
    fn from_bytes(bytes: &[u8]) -> Result<Self, Box<dyn Error>>
    where
        Self: Sized;
}

Description

The main trait that all message types must implement. This trait is automatically implemented for any Copy type that also implements Send + Sync + 'static.The trait provides two methods:
  • to_bytes() - Serializes the message to a byte vector
  • from_bytes() - Deserializes a message from bytes, with automatic relocatable message handling

Automatic Implementation

The trait is automatically implemented for all Copy types:
impl<T> SerializableMessage for T
where
    T: Copy + Send + Sync + 'static,
{
    fn to_bytes(&self) -> Result<Vec<u8>, Box<dyn Error>> {
        Ok(raw::struct_to_bytes(self))
    }

    fn from_bytes(bytes: &[u8]) -> Result<Self, Box<dyn Error>> {
        // Automatically handles relocatable-wrapped messages
        let raw_bytes = match relocatable::parse_message(bytes) {
            Ok(view) => {
                // Try to find a "payload" field
                let mut payload = None;
                for field in view.fields {
                    if let Some(name) = field.name(view.data) {
                        if name == "payload" {
                            if let Some(slice) = field.value_slice(view.data) {
                                payload = Some(slice.to_vec());
                                break;
                            }
                        }
                    }
                }
                payload.unwrap_or_else(|| bytes.to_vec())
            }
            Err(_) => bytes.to_vec(),
        };
        raw::bytes_to_struct(&raw_bytes)
    }
}

Relocatable Message Support

The from_bytes() implementation automatically handles relocatable-wrapped messages:
  1. If bytes are relocatable-wrapped: Extracts the payload field from the relocatable message
  2. If bytes are not relocatable: Uses the bytes directly
This provides backward compatibility with both wrapped and unwrapped message formats.

Example

#[derive(Copy, Clone, Debug)]
#[repr(C)]
struct SensorData {
    temperature: f32,
    timestamp: u64,
}

// Automatically implements SerializableMessage
let data = SensorData {
    temperature: 23.5,
    timestamp: 1234567890,
};

// Serialize
let bytes = data.to_bytes()?;

// Deserialize (handles relocatable messages automatically)
let restored = SensorData::from_bytes(&bytes)?;

See Also

  • Copy Types - Learn about automatic Copy type serialization
  • raw module - Low-level serialization functions

ProtoSerializable Trait

Signature

pub trait ProtoSerializable: ProstMessage + Default + Send + Sync + 'static {
    fn to_proto_bytes(&self) -> Result<Vec<u8>, Box<dyn Error>> {
        let mut buf = Vec::new();
        self.encode(&mut buf)?;
        Ok(buf)
    }

    fn from_proto_bytes(bytes: &[u8]) -> Result<Self, Box<dyn Error>> {
        Ok(Self::decode(bytes)?)
    }
}

Description

Optional trait for types that want to use protobuf serialization instead of raw byte serialization. This is useful for:
  • Cross-language communication (Rust ↔ Python ↔ C++)
  • Schema evolution (adding/removing fields)
  • Complex nested message types
  • When you need guaranteed compatibility
The trait is automatically implemented for all types that implement ProstMessage + Default + Send + Sync + 'static.

Automatic Implementation

impl<T> ProtoSerializable for T 
where 
    T: ProstMessage + Default + Send + Sync + 'static 
{}

When to Use

Use ProtoSerializable when you need:
  • Cross-language compatibility
  • Schema evolution (adding/removing fields over time)
  • Complex nested structures
  • Guaranteed message format compatibility
Use Copy types with automatic SerializableMessage when:
  • Same-language communication only
  • Maximum performance is needed
  • Simple, fixed-size message types

Example

use prost::Message as ProstMessage;

#[derive(Clone, PartialEq, ProstMessage)]
pub struct ProtoSensorData {
    #[prost(uint64, tag = "1")]
    pub timestamp: u64,
    #[prost(float, tag = "2")]
    pub temperature: f32,
    #[prost(float, tag = "3")]
    pub pressure: f32,
}

// Automatically implements ProtoSerializable
let data = ProtoSensorData {
    timestamp: 1234567890,
    temperature: 23.5,
    pressure: 1013.25,
};

// Serialize using protobuf
let bytes = data.to_proto_bytes()?;

// Deserialize
let restored = ProtoSensorData::from_proto_bytes(&bytes)?;

See Also

Raw Module

Signature

pub fn struct_to_bytes<T: Copy>(data: &T) -> Vec<u8>

Description

Converts any Copy type to raw bytes using its memory representation. This function is used internally by the SerializableMessage implementation for Copy types.
This function is safe only for #[repr(C)] structs with simple types (no pointers/references). Using it with unsafe types can lead to memory corruption or undefined behavior.

Parameters

ParameterTypeDescription
data&TReference to a Copy type to serialize

Returns

  • Vec<u8> - Byte vector containing the serialized data

Example

use cerulion_core::serialization::raw;

#[derive(Copy, Clone)]
#[repr(C)]
struct Data {
    value: f32,
}

let data = Data { value: 42.0 };
let bytes = raw::struct_to_bytes(&data);
println!("Size: {} bytes", bytes.len()); // 4 bytes

Implementation Details

The function uses unsafe code to read the memory representation directly:
unsafe {
    let ptr = data as *const T as *const u8;
    std::slice::from_raw_parts(ptr, mem::size_of::<T>()).to_vec()
}
This is safe for #[repr(C)] structs containing only primitive types and fixed-size arrays.

Signature

pub fn bytes_to_struct<T: Copy>(bytes: &[u8]) -> Result<T, Box<dyn Error>>

Description

Converts raw bytes back to a Copy type. This function validates that the byte length matches the type size before deserializing.
This function is safe only for #[repr(C)] structs with simple types. The bytes must exactly match the type’s memory layout.

Parameters

ParameterTypeDescription
bytes&[u8]Byte slice to deserialize

Returns

  • Ok(T) - Successfully deserialized type
  • Err(e) - Error if byte length doesn’t match type size

Example

use cerulion_core::serialization::raw;

#[derive(Copy, Clone, Debug)]
#[repr(C)]
struct Data {
    value: f32,
}

let bytes = vec![0x00, 0x00, 0x28, 0x42]; // 42.0 as f32
match raw::bytes_to_struct::<Data>(&bytes) {
    Ok(data) => println!("Deserialized: {:?}", data),
    Err(e) => eprintln!("Error: {}", e),
}

Error Conditions

The function returns an error if:
  • The byte length doesn’t match mem::size_of::<T>()
  • The bytes cannot be safely interpreted as the target type

Implementation Details

The function validates size and uses unsafe code to read the memory:
if bytes.len() != mem::size_of::<T>() {
    return Err(format!(
        "Invalid byte length: expected {} bytes for type, got {}",
        mem::size_of::<T>(),
        bytes.len()
    ).into());
}

unsafe {
    let ptr = bytes.as_ptr() as *const T;
    Ok(ptr.read())
}

UseProto Marker Trait

Signature

pub trait UseProto {}

Description

Marker trait used internally to disambiguate between Copy types and ProstMessage types. This trait is typically not used directly by end users.

When to Use

Most users don’t need to interact with this trait directly. It’s used internally by the serialization system to determine which serialization method to use.

Next Steps