Tomasz Czajka

Robotik · Async Rust

Warum r2r den klassischen ROS-2-Stack schlägt

ROS 2 ist das Rückgrat moderner Robotik, aber die Art, wie die meisten von uns Nodes schreiben (rclcpp oder rclpy, gebaut mit colcon), ist nicht gut gealtert. r2r bietet idiomatisches Async Rust auf demselben ROS-2-Kern, und wer es einmal ausprobiert hat, will kaum zurück.

Rust r2r ROS 2 Tokio DDS
Humanoider Roboter Atlas, Frontansicht
Humanoider Roboter Atlas. Foto: DARPA, gemeinfrei.

Zuerst: Was r2r eigentlich ist

r2r ersetzt nicht ROS 2 selbst. Es ist eine Rust-Client-Bibliothek, die direkt an rcl bindet, dieselbe C-Schicht, auf der rclcpp und rclpy aufsetzen. Deine Nodes sprechen also weiterhin über DDS mit jedem anderen ROS-2-Node. Was sich ändert, ist alles darüber: die Sprache, das Nebenläufigkeitsmodell und die Build-Werkzeuge. Verglichen wird also r2r mit dem Standardweg, ROS-2-Nodes zu schreiben: C++ oder Python, gebaut mit colcon.

8 Vorteile von r2r

1

Von Grund auf asynchron

Subscriptions sind Streams, Service-Aufrufe und Action-Goals sind Futures. Du kombinierst sie mit tokio::select!, Timeouts und Channels, statt Executors, Callback-Gruppen und zwischen Callbacks geteilten Zustand zu jonglieren.

2

Einfach cargo, kein colcon

Message-Bindings werden beim Build aus der geladenen ROS-2-Umgebung erzeugt. Kein package.xml, keine CMakeLists, kein ament: cargo build, cargo test und rust-analyzer funktionieren einfach.

3

Speicher- und Thread-Sicherheit

Der Compiler schließt Data Races, Use-after-free und hängende Zeiger in Callbacks aus, die klassischen Absturzursachen von C++-Nodes. Bei einem laufenden Humanoiden zählt das.

4

Derselbe ROS-2-Kern darunter

Da r2r rcl kapselt, funktioniert es mit jeder RMW-Implementierung (Fast DDS, Cyclone DDS, Zenoh) und arbeitet mit bestehenden C++- und Python-Nodes zusammen: Topics, Services, Actions, Parameter und Clocks.

5

Native Geschwindigkeit, kein GIL

Kompiliertes Rust mit Zero-Cost-Abstraktionen und ohne Garbage Collector. Anders als bei rclpy bremst kein Global Interpreter Lock hochfrequente Topics, die Latenz bleibt vorhersagbar.

6

Das gesamte crates.io-Ökosystem

serde, nalgebra, tracing, axum, WebRTC: ROS-2-Node, Web-API und Telemetrie-Stream in einer einzigen Binary, ohne ein einziges CMake-find_package.

7

Untypisierte Nachrichten für Bridges

subscribe_untyped und create_publisher_untyped arbeiten über serde mit JSON, sodass generische Bridges, Recorder und Debugging-Tools keine Nachrichtentypen zur Compile-Zeit brauchen.

8

Moderne Werkzeuge und Deployment

clippy, rustfmt, cargo test und reproduzierbare Cargo.lock-Builds in der CI. Liefere eine eigenständige Binary in einem schlanken Container statt eines ganzen install/-Verzeichnisbaums.

So sieht es aus

Ein vollständiger Node: Gelenkzustände abonnieren und Geschwindigkeitsbefehle publizieren, alles in einer Tokio-Runtime. Vergleiche das mit dem entsprechenden rclcpp-Boilerplate.

# Cargo.toml
[dependencies]
r2r = "0.9"
tokio = { version = "1", features = ["full"] }
futures = "0.3"

# build & run: no colcon, no package.xml
$ source /opt/ros/jazzy/setup.bash
$ cargo run --release
use futures::StreamExt;
use r2r::{geometry_msgs::msg::Twist, sensor_msgs::msg::JointState, QosProfile};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let ctx = r2r::Context::create()?;
    let mut node = r2r::Node::create(ctx, "teleop_bridge", "")?;

    let mut joints = node.subscribe::<JointState>("/joint_states", QosProfile::default())?;
    let cmd = node.create_publisher::<Twist>("/cmd_vel", QosProfile::default())?;

    // rcl spins on its own thread, everything else is plain async Rust
    tokio::task::spawn_blocking(move || loop {
        node.spin_once(std::time::Duration::from_millis(5));
    });

    while let Some(state) = joints.next().await {
        let mut twist = Twist::default();
        twist.linear.x = state.velocity.first().copied().unwrap_or(0.0) * 0.5;
        cmd.publish(&twist)?;
    }
    Ok(())
}

Fairerweise: die Kompromisse

  • r2r wird von der Community gepflegt und gehört nicht zu den offiziellen ROS-2-Client-Bibliotheken, daher gibt es weniger Tutorials und Antworten als für rclcpp oder rclpy.
  • Zum Bauen ist eine geladene ROS-2-Installation nötig, da die Bindings aus den gefundenen Message-Paketen erzeugt werden.
  • Der erste Build ist langsam, weil bindgen und die Message-Generierung für jedes Paket laufen. Inkrementelle Builds sind unproblematisch.
  • Launch-Dateien und colcon-Workspaces brauchen etwas Klebstoff (z. B. colcon-ros-cargo), um ein cargo-Crate als ROS-Paket zu behandeln.

Fazit

Für neue Middleware, Bridges, Teleoperation und Steuerungspipelines ist r2r die bessere Standardwahl: Asynchronität, die wirklich zu Robotik-I/O passt, Sicherheit zur Compile-Zeit und ein Build, der einfach cargo ist. rclcpp bleibt für Legacy-Code und Herstellertreiber; für alles Neue nimm r2r.