# cyclonedds-rust
[](LICENSE-MIT)
Safe, idiomatic Rust bindings for [Eclipse CycloneDDS](https://github.com/eclipse-cyclonedds/cyclonedds) — a high-performance implementation of the OMG Data Distribution Service (DDS) specification.
## Highlights
- **Complete DDS entity model** — DomainParticipant, Publisher, Subscriber, Topic, DataWriter, DataReader
- **26+ QoS policies** via a type-safe `QosBuilder` pattern
- **13 listener callbacks** via `ListenerBuilder` (data available, matched, liveliness, deadline, etc.)
- **WaitSet / ReadCondition / QueryCondition / GuardCondition** for event-driven architectures
- **Derive macros** for topic types: `DdsType`, `DdsEnum`, `DdsUnion`, `DdsBitmask`
- **CDR serialization** (XCDR1/XCDR2), dynamic types, type discovery (XTypes)
- **Async Streams** (`read_aiter`, `take_aiter`) with tokio integration
- **Async Timeouts & Cancellation** (`read_aiter_timeout`, `take_aiter_timeout`) with safe cancellation
- **Zero-Copy Loans** (`write_loan`, `read_loan`, `take_loan`) for minimal latency
- **DDS Security** (`SecurityConfig`) with X.509 certificate validation and hot-reload
- **ROS2 Interop** helpers (`ros2_topic_name`, `ros2_qos_reliable`) for seamless ROS2 integration
- **Diagnostics CLI** (`diagnose`, `metrics`) with Prometheus export support
- **`tracing` Integration** for structured logs and distributed spans
- **WASM (Experimental)** — `cyclonedds-wasm` crate for browser-based DDS over WebSocket
- **no_std / Embedded (Experimental)** — define DDS types without `std` for embedded targets
## Quick Start
Add to your `Cargo.toml`:
```toml
[dependencies]
cyclonedds = "1.8"
```
### Define a Topic Type
```rust
use cyclonedds::*;
#[repr(C)]
struct HelloWorld {
id: i32,
message: [u8; 256],
}
impl DdsType for HelloWorld {
fn type_name() -> &'static str {
"HelloWorld"
}
fn ops() -> Vec<u32> {
let mut ops = Vec::new();
ops.extend(adr(TYPE_4BY | OP_FLAG_SGN, 0));
ops.extend(adr_bst(4, 256));
ops
}
}
```
### Publisher
```rust
fn main() -> Result<(), Box<dyn std::error::Error>> {
let dp = DomainParticipant::new(0)?;
let pub_ = Publisher::new(dp.entity())?;
let topic = Topic::<HelloWorld>::new(dp.entity(), "Hello")?;
let writer = DataWriter::new(pub_.entity(), topic.entity())?;
let mut msg = HelloWorld { id: 1, message: [0; 256] };
msg.message[..5].copy_from_slice(b"hello");
writer.write(&msg)?;
Ok(())
}
```
### Subscriber
```rust
fn main() -> Result<(), Box<dyn std::error::Error>> {
let dp = DomainParticipant::new(0)?;
let sub = Subscriber::new(dp.entity())?;
let topic = Topic::<HelloWorld>::new(dp.entity(), "Hello")?;
let reader = DataReader::<HelloWorld>::new(sub.entity(), topic.entity())?;
loop {
for s in reader.take()? {
println!("id={}", s.id);
}
}
}
```
## Async Streams
When the `async` feature is enabled (default), `DataReader` provides async iterators over incoming samples:
```rust
use cyclonedds::DataReader;
use futures_util::StreamExt;
async fn consume<T: cyclonedds::DdsType>(reader: &DataReader<T>) {
let mut stream = Box::pin(reader.read_aiter());
while let Some(batch) = stream.next().await {
match batch {
Ok(samples) => println!("got {} samples", samples.len()),
Err(e) => eprintln!("read error: {}", e),
}
}
}
```
## Feature Matrix
| Core Entities | Yes | Partial | **Yes** |
| QoS (26+) | Yes | Partial | **Yes** |
| Listeners (13) | Yes | Partial | **Yes** |
| WaitSet / Conditions | Yes | No | **Yes** |
| CDR Serialization (XCDR1/2) | Yes | Yes | **Yes** |
| Dynamic Types & Data | Yes | No | **Yes** |
| Type Discovery (XTypes) | Yes | No | **Yes** |
| Content-Filtered Topics | Yes | Partial | **Yes** (closure-based) |
| Union / Bitmask / Enum | Yes | Partial | **Yes** |
| IDL Compilation | Yes | Yes | **Yes** |
| CLI Tools | Yes | No | **Yes** (`ls`, `ps`, `subscribe`, `typeof`, `publish`, `discover`, `echo`, `record`, `replay`, `monitor`, `health`, `topology`) |
| Async Streams (`read_aiter`, `take_aiter`) | No | No | **Yes** |
| Matched Endpoint Data | Yes | No | **Yes** |
| Zero-copy Write Loan | No | Yes | **Yes** |
| DDS Security | Yes | No | **Yes** (`SecurityConfig` + `--features security`) |
| Request-Reply Pattern | No | No | **Yes** (`Requester` / `Replier`) |
| Connection Pooling | No | No | **Yes** (`ParticipantPool`) |
| Serde Integration | No | No | **Yes** (`SerdeSample<T>` + `--features serde`) |
| WASM (Experimental) | No | No | **Yes** (`cyclonedds-wasm` crate) |
| no_std / Embedded (Experimental) | No | No | **Yes** (`--features no_std`) |
## Workspace Crates
| `cyclonedds-sys` | Low-level FFI bindings (generated via bindgen) |
| `cyclonedds` | High-level safe Rust API |
| `cyclonedds-derive` | Procedural derive macros (`DdsType`, `DdsEnum`, `DdsUnion`, `DdsBitmask`) |
| `cyclonedds-build` | Build-time helpers for generating types from IDL |
| `cyclonedds-idlc` | IDL compiler backend producing Rust source from IDL files |
| `cyclonedds-cli` | Command-line tools (`ls`, `ps`, `subscribe`, `typeof`, `publish`, `perf`, `discover`, `echo`, `record`, `replay`, `monitor`, `health`, `topology`) |
| `cargo-cyclonedds` | Cargo plugin (`cargo cyclonedds generate <idl>`) |
| `cyclonedds-bench` | Criterion benchmarks (latency, throughput, CDR) |
| `cyclonedds-test-suite` | Integration tests |
| `cyclonedds-wasm` | Experimental WebAssembly bindings (WebSocket transport) |
## Build
```bash
cargo build --workspace # build everything
cargo test --workspace # run tests
cargo build --workspace --release
```
### Requirements
- Rust 1.85+ (MSRV)
- CMake 3.10+
- C/C++ compiler
> **Note:** Clang is no longer required for end users. Prebuilt FFI bindings are shipped with the crate. Clang is only needed if you are a maintainer regenerating bindings (see `scripts/regenerate-bindings.sh`).
The bundled CycloneDDS source in `cyclonedds-src` is built automatically by `cyclonedds-rust-sys` when CMake is available.
### WSL Notes
If building in WSL, ensure `libddsc.so` is discoverable after the first build:
```bash
export LD_LIBRARY_PATH=~/cyclonedds-rust/vendor/cyclonedds/build/lib:$LD_LIBRARY_PATH
cargo test --workspace --features async
```
## CLI Examples
```bash
# List all topics in a domain
cargo run --bin cyclonedds-cli -- ls --domain 0
# Show participant status
cargo run --bin cyclonedds-cli -- ps --domain 0
# Subscribe to a topic
cargo run --bin cyclonedds-cli -- subscribe --topic HelloWorld
# Subscribe with JSON output and filter
cargo run --bin cyclonedds-cli -- subscribe --topic HelloWorld --json --filter "id > 10"
# Show type info
cargo run --bin cyclonedds-cli -- typeof --topic HelloWorld
# Publish at 10 Hz
cargo run --bin cyclonedds-cli -- publish --topic HelloWorld --message "hi" --rate 10
# Monitor throughput
cargo run --bin cyclonedds-cli -- monitor --topic HelloWorld
# Health check
cargo run --bin cyclonedds-cli -- health "HelloWorld,AnotherTopic"
# Generate topology graph
cargo run --bin cyclonedds-cli -- topology --output topology.dot
# Subscribe to multiple topics simultaneously
cargo run --bin cyclonedds-cli -- subscribe --topics "TopicA,TopicB" --json
# Bridge samples from one topic to another (optionally across domains)
cargo run --bin cyclonedds-cli -- bridge TopicA TopicB --domain-src 0 --domain-dst 1
```
## Examples
```bash
# Terminal 1 - subscriber
cargo run --example sub
# Terminal 2 - publisher
cargo run --example pub
```
## Documentation
- [Getting Started](docs/getting-started.md) — installation, first steps, WSL notes
- [Tutorial](docs/tutorial.md) — step-by-step first DDS application
- [API Guide](docs/api-guide.md) — tour of all major API features
- [Type System](docs/type-system.md) — `DdsType` derive, supported types, CDR encoding
- [QoS Reference](docs/qos-reference.md) — all QoS policies and builder patterns
- [ROS2 Integration](docs/ros2-integration.md) — communicating with ROS2 nodes
- [Security Guide](docs/security-guide.md) — DDS Security setup and certificates
- [Observability](docs/observability.md) — `tracing` integration for structured logs and spans
- [Benchmarks](docs/benchmarks.md) — running performance benchmarks and comparisons
- [Fuzzing](docs/fuzzing.md) — automated fuzz testing with `cargo-fuzz`
- [FAQ](docs/faq.md) — frequently asked questions and troubleshooting
- [Migration from Python](docs/migration-from-python.md) — guide for `cyclonedds-python` users
## WASM Support (Experimental)
The `cyclonedds-wasm` crate provides a DDS-compatible API for WebAssembly:
```toml
[dependencies]
cyclonedds-wasm = "0.1"
```
```rust
use cyclonedds_wasm::*;
let participant = WasmDomainParticipant::new("ws://localhost:8080/dds").unwrap();
let topic = participant.create_topic::<MyMessage>("HelloWorld").unwrap();
let writer = participant.create_writer(&topic).unwrap();
writer.write(&MyMessage { id: 1, text: "hello".to_string() }).unwrap();
```
> **Note:** This is not a full DDS implementation. It uses JSON over WebSocket rather than RTPS/CDR. A DDS-to-WebSocket bridge is required to communicate with native DDS participants.
## Embedded / no_std (Experimental)
When the `no_std` feature is enabled, `cyclonedds` exports only pure-Rust DDS types and constants without the CycloneDDS C FFI:
```toml
[dependencies]
cyclonedds = { version = "1.8", default-features = false, features = ["no_std"] }
```
```rust
#![no_std]
extern crate alloc;
use cyclonedds::{DdsType, adr, TYPE_4BY, OP_RTS};
#[repr(C)]
pub struct SensorReading {
pub id: i32,
pub value: f32,
}
impl DdsType for SensorReading {
fn type_name() -> &'static str { "SensorReading" }
fn ops() -> alloc::vec::Vec<u32> {
let mut ops = alloc::vec::Vec::new();
ops.extend(adr(TYPE_4BY | (1 << 2), 0)); // id @ offset 0
ops.extend(adr(TYPE_4BY, 4)); // value @ offset 4
ops.push(OP_RTS);
ops
}
}
```
This is useful for defining DDS-compatible types on embedded systems (e.g., `thumbv7em-none-eabihf`) where the full CycloneDDS C library cannot run. Actual CDR serialization must be performed manually or via a separate no-std serializer.
## Known Limitations
- **CLI `publish`:** Supports string messages, JSON payloads, and dynamic types discovered at runtime. Complex nested structs may require using the Rust API directly for full control.
- **DDS Security on Windows:** Requires OpenSSL to be installed and `OPENSSL_ROOT_DIR` configured. The `security` feature is disabled by default on Windows CI to avoid build issues.
- **WASM:** Not a full DDS implementation — requires a WebSocket bridge to communicate with native DDS.
- **no_std:** No actual DDS networking. Only type definitions and CDR opcode constants are available.
## Benchmarks
```bash
cargo test --test write_loan # zero-copy write test
cargo test --test interop # cross-process pub/sub test
cargo run --example interop_pub # standalone publisher
cargo run --example interop_sub # standalone subscriber
```
## License
Licensed under the [MIT License](LICENSE-MIT).
## Acknowledgments
Built on [Eclipse CycloneDDS](https://github.com/eclipse-cyclonedds/cyclonedds) — a high-performance DDS implementation.