Rust — in 5 Minuten zum ersten Sample
Primärpfad — der ganze ZeroDDS-Stack ist Pure-Rust. zerodds-dcps als einzige Dependency, alles andere wird transitiv gezogen. Async-friendly (Streams + Futures), IDL-Codegen über zerodds-build im build.rs, Basis-Crates no_std-tauglich.
5-Minuten-Quickstart
Ein cargo new, eine Dep, ~15 Zeilen Rust.
1 · Projekt + Dep
cargo new zerodds-hello --bin
cd zerodds-hello
cargo add zerodds-dcps
2 · Pub/Sub-Roundtrip (src/main.rs)
use std::time::Duration;
use zerodds_dcps::*;
fn main() -> Result<()> {
let factory = DomainParticipantFactory::instance();
let p = factory.create_participant(0, DomainParticipantQos::default())?;
let topic = p.create_topic::<RawBytes>("Chatter", TopicQos::default())?;
let writer = p.create_publisher(PublisherQos::default())
.create_datawriter::<RawBytes>(&topic, DataWriterQos::default())?;
let reader = p.create_subscriber(SubscriberQos::default())
.create_datareader::<RawBytes>(&topic, DataReaderQos::default())?;
writer.wait_for_matched_subscription(1, Duration::from_secs(5))?;
reader.wait_for_matched_publication(1, Duration::from_secs(5))?;
writer.write(&RawBytes::new(b"hello".to_vec()))?;
reader.wait_for_data(Duration::from_secs(3))?;
for sample in reader.take()? {
println!("got: {:?}", sample.data);
}
Ok(())
}
▶ Runnable example: bindings-rust-main
3 · Laufen
cargo run --release
# got: [104, 101, 108, 108, 111]
Installation
Cargo (Standard)
[dependencies]
zerodds-dcps = "1.0.0-rc.7"
Zieht seine Abhängigkeiten transitiv, darunter foundation, wire, qos, types, rtps, discovery, transport und transport-udp.
Direct Layer-Crate Pulls
Für no_std-Embedded reichen die Basis-Crates:
[dependencies]
zerodds-foundation = "1.0.0-rc.7"
zerodds-wire = "1.0.0-rc.7"
zerodds-qos = "1.0.0-rc.7"
zerodds-types = "1.0.0-rc.7"
Aus dem Source
Siehe Dev-Install — From Source.
Typisierte Topics — DdsType
Der Typ kommt aus IDL. zerodds-build übersetzt sie im build.rs; mit dds_type_impls(true) schreibt es neben Typ und Codec auch die zerodds_dcps::DdsType-Implementierung, die create_topic verlangt. Ein #[derive(DdsType)] gibt es nicht mehr.
IDL (temperature.idl)
module sensor_msgs {
module msg {
struct Temperature {
long celsius;
string sensor_id;
};
};
};
Cargo.toml
[dependencies]
zerodds-dcps = "1.0.0-rc.7"
zerodds-types = "1.0.0-rc.7"
zerodds-wire = "1.0.0-rc.7"
[build-dependencies]
zerodds-build = "1.0.0-rc.7"
build.rs
fn main() -> Result<(), zerodds_build::Error> {
zerodds_build::Config::new()
.dds_type_impls(true)
.compile(&["temperature.idl"])
}
Nutzen (src/main.rs)
#[allow(non_snake_case, unused_imports, dead_code)]
mod generated {
include!(concat!(env!("OUT_DIR"), "/temperature.rs"));
}
use generated::sensor_msgs::msg::Temperature;
// Nutzen
let topic = p.create_topic::<Temperature>("Temp", TopicQos::default())?;
let writer = p.create_publisher(PublisherQos::default())
.create_datawriter::<Temperature>(&topic, DataWriterQos::default())?;
writer.write(&Temperature { celsius: 23, sensor_id: "A7".into() })?;
▶ Runnable example: rust-typed
Ohne build.rs: die Kommandozeile
zerodds-idlc generate --lang rust --out gen/ temperature.idl
# schreibt gen/temperature.rs
Die Kommandozeile schreibt Typ und Codec, aber keine DdsType-Implementierung. Die schreibt nur zerodds-build mit dds_type_impls(true).
async / Streams
ZeroDDS hat einen separaten Async-Adapter-Crate (zerodds-dcps-async) — wraps die sync-API mit Tokio-Tasks.
use std::time::Duration;
use zerodds_dcps_async::*;
use zerodds_dcps::RawBytes;
#[tokio::main]
async fn main() -> Result<()> {
let factory = AsyncDomainParticipantFactory::instance();
let p = factory.create_participant(0)?;
let topic = p.create_topic::<RawBytes>("Chatter", TopicQos::default())?;
let writer = p.create_publisher(PublisherQos::default())
.create_datawriter::<RawBytes>(&topic, DataWriterQos::default())?;
let reader = p.create_subscriber(SubscriberQos::default())
.create_datareader::<RawBytes>(&topic, DataReaderQos::default())?;
writer.wait_for_matched_subscription(1, Duration::from_secs(5)).await?;
reader.wait_for_matched_publication(1, Duration::from_secs(5)).await?;
writer.write(&RawBytes::new(b"hello".to_vec())).await?;
for sample in reader.take(Duration::from_secs(3)).await? {
println!("got: {:?}", sample.data);
}
Ok(())
}
no_std + alloc
Die Basis-Crates (foundation, wire, qos, types) sind no_std-tauglich mit alloc. zerodds-wire ist immer no_std + alloc und hat keine Features:
[dependencies]
zerodds-foundation = { version = "1.0.0-rc.7", default-features = false, features = ["alloc"] }
zerodds-wire = "1.0.0-rc.7"
Strict no_std (kein Heap):
zerodds-foundation = { version = "1.0.0-rc.7", default-features = false }
Details: Dev-Install — Feature-Flags.
Rust — first sample in 5 minutes
The primary path — the whole ZeroDDS stack is pure Rust. zerodds-dcps as the single dependency, everything else pulled in transitively. Async-friendly (streams + futures), IDL codegen via zerodds-build in build.rs, no_std-capable base crates.
5-minute quickstart
One cargo new, one dep, ~15 lines of Rust.
1 · Project + dep
cargo new zerodds-hello --bin
cd zerodds-hello
cargo add zerodds-dcps
2 · Pub/sub roundtrip (src/main.rs)
use std::time::Duration;
use zerodds_dcps::*;
fn main() -> Result<()> {
let factory = DomainParticipantFactory::instance();
let p = factory.create_participant(0, DomainParticipantQos::default())?;
let topic = p.create_topic::<RawBytes>("Chatter", TopicQos::default())?;
let writer = p.create_publisher(PublisherQos::default())
.create_datawriter::<RawBytes>(&topic, DataWriterQos::default())?;
let reader = p.create_subscriber(SubscriberQos::default())
.create_datareader::<RawBytes>(&topic, DataReaderQos::default())?;
writer.wait_for_matched_subscription(1, Duration::from_secs(5))?;
reader.wait_for_matched_publication(1, Duration::from_secs(5))?;
writer.write(&RawBytes::new(b"hello".to_vec()))?;
reader.wait_for_data(Duration::from_secs(3))?;
for sample in reader.take()? {
println!("got: {:?}", sample.data);
}
Ok(())
}
▶ Runnable example: bindings-rust-main
3 · Run
cargo run --release
# got: [104, 101, 108, 108, 111]
Installation
Cargo (standard)
[dependencies]
zerodds-dcps = "1.0.0-rc.7"
Pulls in its dependencies transitively, among them foundation, wire, qos, types, rtps, discovery, transport and transport-udp.
Direct layer-crate pulls
For no_std embedded the base crates are enough:
[dependencies]
zerodds-foundation = "1.0.0-rc.7"
zerodds-wire = "1.0.0-rc.7"
zerodds-qos = "1.0.0-rc.7"
zerodds-types = "1.0.0-rc.7"
From source
Typed topics — DdsType
The type comes from IDL. zerodds-build compiles it in build.rs; with dds_type_impls(true) it writes, beside the type and its codec, the zerodds_dcps::DdsType impl that create_topic takes. There is no #[derive(DdsType)] any more.
IDL (temperature.idl)
module sensor_msgs {
module msg {
struct Temperature {
long celsius;
string sensor_id;
};
};
};
Cargo.toml
[dependencies]
zerodds-dcps = "1.0.0-rc.7"
zerodds-types = "1.0.0-rc.7"
zerodds-wire = "1.0.0-rc.7"
[build-dependencies]
zerodds-build = "1.0.0-rc.7"
build.rs
fn main() -> Result<(), zerodds_build::Error> {
zerodds_build::Config::new()
.dds_type_impls(true)
.compile(&["temperature.idl"])
}
Use it (src/main.rs)
#[allow(non_snake_case, unused_imports, dead_code)]
mod generated {
include!(concat!(env!("OUT_DIR"), "/temperature.rs"));
}
use generated::sensor_msgs::msg::Temperature;
// use it
let topic = p.create_topic::<Temperature>("Temp", TopicQos::default())?;
let writer = p.create_publisher(PublisherQos::default())
.create_datawriter::<Temperature>(&topic, DataWriterQos::default())?;
writer.write(&Temperature { celsius: 23, sensor_id: "A7".into() })?;
▶ Runnable example: rust-typed
Without build.rs: the command line
zerodds-idlc generate --lang rust --out gen/ temperature.idl
# writes gen/temperature.rs
The command line writes the type and its codec, but no DdsType impl. Only zerodds-build with dds_type_impls(true) writes that.
async / streams
ZeroDDS has a separate async adapter crate (zerodds-dcps-async) — it wraps the sync API with Tokio tasks.
use std::time::Duration;
use zerodds_dcps_async::*;
use zerodds_dcps::RawBytes;
#[tokio::main]
async fn main() -> Result<()> {
let factory = AsyncDomainParticipantFactory::instance();
let p = factory.create_participant(0)?;
let topic = p.create_topic::<RawBytes>("Chatter", TopicQos::default())?;
let writer = p.create_publisher(PublisherQos::default())
.create_datawriter::<RawBytes>(&topic, DataWriterQos::default())?;
let reader = p.create_subscriber(SubscriberQos::default())
.create_datareader::<RawBytes>(&topic, DataReaderQos::default())?;
writer.wait_for_matched_subscription(1, Duration::from_secs(5)).await?;
reader.wait_for_matched_publication(1, Duration::from_secs(5)).await?;
writer.write(&RawBytes::new(b"hello".to_vec())).await?;
for sample in reader.take(Duration::from_secs(3)).await? {
println!("got: {:?}", sample.data);
}
Ok(())
}
no_std + alloc
The base crates (foundation, wire, qos, types) are no_std-capable with alloc. zerodds-wire is always no_std + alloc and has no features:
[dependencies]
zerodds-foundation = { version = "1.0.0-rc.7", default-features = false, features = ["alloc"] }
zerodds-wire = "1.0.0-rc.7"
Strict no_std (no heap):
zerodds-foundation = { version = "1.0.0-rc.7", default-features = false }
Details: Dev Install — feature flags.