Rust Examples

Working source for each example lives under examples/rust/ in the AstuteDDS source tree.

Hello World

A two-binary crate that publishes and consumes UTF-8 strings on HelloWorldTopic.

  • examples/rust/hello_world/src/hello_publisher.rs
  • examples/rust/hello_world/src/hello_subscriber.rs

See Quick Start for the full source.

Sensor (multi-field CDR)

TemperatureSensor carries a 24-byte XCDR1 little-endian payload:

Offset Field Type
0 CDR header 4 B (00 01 00 00)
4 sensor_id u32
8 temperature_c f32
12 value_sine f32
16 value_triangle f32
20 sequence u32

Encode

fn encode_sensor_reading(
    sensor_id:      u32,
    temperature_c:  f32,
    value_sine:     f32,
    value_triangle: f32,
    sequence:       u32,
) -> [u8; 24] {
    let mut buf = [0u8; 24];
    buf[0..4].copy_from_slice(&[0x00, 0x01, 0x00, 0x00]);    // XCDR1 LE header
    buf[4..8].copy_from_slice(&sensor_id.to_le_bytes());
    buf[8..12].copy_from_slice(&temperature_c.to_le_bytes());
    buf[12..16].copy_from_slice(&value_sine.to_le_bytes());
    buf[16..20].copy_from_slice(&value_triangle.to_le_bytes());
    buf[20..24].copy_from_slice(&sequence.to_le_bytes());
    buf
}

Decode

struct SensorReading {
    sensor_id:      u32,
    temperature_c:  f32,
    value_sine:     f32,
    value_triangle: f32,
    sequence:       u32,
}

fn decode_sensor_reading(raw: &[u8]) -> Option<SensorReading> {
    if raw.len() < 24 { return None; }
    Some(SensorReading {
        sensor_id:      u32::from_le_bytes(raw[4..8].try_into().ok()?),
        temperature_c:  f32::from_le_bytes(raw[8..12].try_into().ok()?),
        value_sine:     f32::from_le_bytes(raw[12..16].try_into().ok()?),
        value_triangle: f32::from_le_bytes(raw[16..20].try_into().ok()?),
        sequence:       u32::from_le_bytes(raw[20..24].try_into().ok()?),
    })
}

Poll loop with timeout

use astutedds::{DataReaderQos, DomainParticipant, Error, HistoryKind, Reliability};
use std::time::{Duration, Instant};

fn main() -> astutedds::Result<()> {
    let dp     = DomainParticipant::new(0)?;
    let topic  = dp.create_topic("TemperatureSensor", "SensorReading")?;
    let sub    = dp.create_subscriber()?;
    let reader = sub.create_datareader(&topic, DataReaderQos {
        reliability:   Reliability::ASTUTEDDS_RELIABLE,
        history_kind:  HistoryKind::ASTUTEDDS_KEEP_LAST,
        history_depth: 10,
        ..Default::default()
    })?;

    const SILENCE: Duration = Duration::from_secs(60);
    let mut last = Instant::now();

    loop {
        match reader.take_next() {
            Ok(bytes) => {
                last = Instant::now();
                if let Some(s) = decode_sensor_reading(&bytes) {
                    println!("seq={:>5}  temp={:.2} °C", s.sequence, s.temperature_c);
                }
            }
            Err(Error::NoData) => {
                if last.elapsed() > SILENCE {
                    println!("(silent for 60 s — exiting)");
                    break;
                }
                std::thread::sleep(Duration::from_millis(100));
            }
            Err(e) => return Err(e),
        }
    }
    Ok(())
}

QoS overrides

DataWriterQos and DataReaderQos are Copy structs — clone-and-override with struct-update syntax:

use astutedds::{DataWriterQos, Durability, HistoryKind, Reliability};

let qos = DataWriterQos {
    reliability:   Reliability::ASTUTEDDS_RELIABLE,
    history_kind:  HistoryKind::ASTUTEDDS_KEEP_LAST,
    history_depth: 100,
    durability:    Durability::ASTUTEDDS_TRANSIENT_LOCAL,
};

Sharing entities across threads

All wrapper types are Send + Sync, so you can share them with Arc:

use std::sync::Arc;
use std::thread;

let dp     = Arc::new(DomainParticipant::new(0)?);
let topic  = dp.create_topic("Heartbeat", "Heartbeat")?;
let pub_   = dp.create_publisher()?;
let writer = Arc::new(pub_.create_datawriter(&topic, Default::default())?);

let w = writer.clone();
thread::spawn(move || {
    for i in 0..100 {
        let _ = w.write(format!("beat {i}").as_bytes());
    }
});

Note

Lifetimes still apply — the Topic, Publisher, and DataWriter must not outlive the DomainParticipant. When sharing across threads, keep the participant alive in the outermost scope (or via its own Arc).

Multi-domain isolation

let dp_lab  = DomainParticipant::new(0)?;
let dp_sim  = DomainParticipant::new(42)?;

Participants in different domain_ids exchange no traffic — useful for isolating staging, integration, and production traffic on the same host.