Skip to main content

Complete Runnable Example

Copy and run this example to see messages being sent and received using direct Publisher and Subscriber APIs.
use cerulion_core::prelude::*;

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

// Implement SerializableMessage for serialization
impl SerializableMessage for SensorData {
    fn to_bytes(&self) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
        let mut bytes = Vec::with_capacity(12);
        bytes.extend_from_slice(&self.temperature.to_le_bytes());
        bytes.extend_from_slice(&self.timestamp.to_le_bytes());
        Ok(bytes)
    }
}

// Implement deserialization
impl SensorData {
    fn from_bytes(bytes: &[u8]) -> Result<Self, Box<dyn std::error::Error>> {
        if bytes.len() < 12 {
            return Err("Invalid byte length".into());
        }
        let temperature = f32::from_le_bytes([
            bytes[0], bytes[1], bytes[2], bytes[3]
        ]);
        let timestamp = u64::from_le_bytes([
            bytes[4], bytes[5], bytes[6], bytes[7],
            bytes[8], bytes[9], bytes[10], bytes[11]
        ]);
        Ok(SensorData { temperature, timestamp })
    }
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Create publisher with network enabled
    let publisher = Publisher::create("sensors/temperature", true)?;
    
    // Create subscriber (auto-detects local transport)
    let subscriber = Subscriber::create("sensors/temperature", None)?;
    
    // Send multiple messages
    for i in 0..10 {
        let data = SensorData {
            temperature: 20.0 + (i as f32) * 0.5,
            timestamp: i as u64,
        };
        // Use send_message for convenience, or send() with raw bytes
        publisher.send_message(&data)?;
        
        // Small delay to see messages arrive
        std::thread::sleep(std::time::Duration::from_millis(100));
    }
    
    // Receive messages
    let mut count = 0;
    loop {
        if let Ok(Some(bytes)) = subscriber.receive() {
            // Deserialize the raw bytes
            if let Ok(data) = SensorData::from_bytes(&bytes) {
                println!("Message {}: temp={}°C, time={}", 
                         count, data.temperature, data.timestamp);
                count += 1;
                
                if count >= 10 {
                    break;
                }
            }
        }
    }
    
    Ok(())
}

Walkthrough

1. Define Your Message Type

#[derive(Copy, Clone, Debug)]
#[repr(C)]
struct SensorData {
    temperature: f32,
    timestamp: u64,
}
Define your message struct. The #[repr(C)] attribute ensures a predictable memory layout for cross-language compatibility.

2. Implement Serialization

impl SerializableMessage for SensorData {
    fn to_bytes(&self) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
        let mut bytes = Vec::with_capacity(12);
        bytes.extend_from_slice(&self.temperature.to_le_bytes());
        bytes.extend_from_slice(&self.timestamp.to_le_bytes());
        Ok(bytes)
    }
}
Implement SerializableMessage to convert your struct to bytes. This enables the send_message() convenience method.

3. Implement Deserialization

impl SensorData {
    fn from_bytes(bytes: &[u8]) -> Result<Self, Box<dyn std::error::Error>> {
        // Validate length and deserialize
        // ...
    }
}
Implement a from_bytes() method to convert raw bytes back to your struct. This is called when receiving messages.

4. Create a Publisher

let publisher = Publisher::create("sensors/temperature", true)?;
Creates a publisher for the topic. The second parameter (true) enables network transport. Publishers always use local (iceoryx2) transport; network is optional.
The publisher handle must remain alive for subscribers to receive messages. If the publisher is dropped, local subscribers will stop receiving messages.

5. Create a Subscriber

let subscriber = Subscriber::create("sensors/temperature", None)?;
Creates a subscriber with auto-detection (None). The subscriber will:
  1. Try local transport first (if a local publisher exists)
  2. Fall back to network transport if no local publisher is found

6. Send Messages

let data = SensorData {
    temperature: 20.0 + (i as f32) * 0.5,
    timestamp: i as u64,
};
publisher.send_message(&data)?;
Sends a typed message. The send_message() method automatically serializes using your to_bytes() implementation. Alternatively, use send() with raw bytes.
The send() and send_message() methods return immediately. For local subscribers, the message is available in < 1 μs. For network subscribers, serialization and network send happen asynchronously on a background thread.

7. Receive Messages

match subscriber.receive() {
    Ok(Some(bytes)) => {
        // Deserialize the raw bytes
        if let Ok(data) = SensorData::from_bytes(&bytes) {
            // Process message
        }
    }
    Ok(None) => {
        // No message available (non-blocking)
    }
    Err(e) => {
        // Handle error
    }
}
receive() is non-blocking and returns raw bytes. You must deserialize them using your from_bytes() method.
The receive() method never blocks. It returns immediately with Some(Vec<u8>) if a message is available, or None if no message is ready.

8. Polling Pattern

For continuous message reception, use a polling loop:
loop {
    if let Ok(Some(bytes)) = subscriber.receive() {
        if let Ok(data) = SensorData::from_bytes(&bytes) {
            process_sensor_data(data);
        }
    }
    
    // Small sleep to avoid busy-waiting
    std::thread::sleep(std::time::Duration::from_millis(1));
}
For high-frequency data, consider removing the sleep. The receive() call is very fast (< 1 μs for local transport), so busy-waiting may be acceptable for real-time applications.

What to Try Next

  • Run the publisher and subscriber in separate processes - See how network transport works automatically
  • Control network enable/disable - Use enable_network() and disable_network() to control network publishing
  • Use raw bytes - Try using send() with raw bytes instead of send_message()
  • Force transport modes - Experiment with Some(true) (network only) or Some(false) (local only) for subscribers
  • Compare with TopicManager - See how TopicManager simplifies this workflow with automatic management

Next Steps