Tomasz Czajka

Robotyka · Async Rust

Dlaczego r2r wygrywa z klasycznym stosem ROS 2

ROS 2 to kręgosłup współczesnej robotyki, ale sposób, w jaki większość z nas pisze węzły (rclcpp lub rclpy budowane przez colcon), nie zestarzał się najlepiej. r2r daje idiomatyczny async Rust na tym samym rdzeniu ROS 2, a gdy raz spróbujesz, trudno wrócić.

Rust r2r ROS 2 Tokio DDS
Robot humanoidalny Atlas, widok z przodu
Robot humanoidalny Atlas. Zdjęcie: DARPA, domena publiczna.

Najpierw: czym właściwie jest r2r

r2r nie zastępuje samego ROS 2. To biblioteka kliencka Rust, która wiąże się bezpośrednio z rcl, tą samą warstwą C, na której stoją rclcpp i rclpy, więc Twoje węzły nadal rozmawiają z każdym innym węzłem ROS 2 przez DDS. Zmienia się wszystko powyżej tej warstwy: język, model współbieżności i narzędzia budowania. To porównanie dotyczy więc r2r i standardowego sposobu pisania węzłów ROS 2: C++ lub Pythona budowanych przez colcon.

8 zalet r2r

1

Asynchroniczność od podstaw

Subskrypcje to Streamy, wywołania usług i cele akcji to Future. Łączysz je przez tokio::select!, timeouty i kanały zamiast żonglować executorami, grupami callbacków i stanem współdzielonym między callbackami.

2

Zwykłe cargo, bez colcon

Wiązania wiadomości są generowane podczas budowania z załadowanego środowiska ROS 2. Bez package.xml, CMakeLists i ament: cargo build, cargo test i rust-analyzer po prostu działają.

3

Bezpieczeństwo pamięci i wątków

Kompilator wyklucza wyścigi danych, use-after-free i wiszące wskaźniki w callbackach, czyli klasyczne przyczyny awarii węzłów C++. Przy chodzącym humanoidzie to ma znaczenie.

4

Ten sam rdzeń ROS 2 pod spodem

Ponieważ r2r opakowuje rcl, działa z każdą implementacją RMW (Fast DDS, Cyclone DDS, Zenoh) i współpracuje z istniejącymi węzłami C++ i Python: topiki, usługi, akcje, parametry i zegary.

5

Natywna szybkość, bez GIL

Kompilowany Rust z abstrakcjami o zerowym koszcie i bez garbage collectora. W przeciwieństwie do rclpy nie ma Global Interpreter Lock dławiącego topiki o wysokiej częstotliwości, więc opóźnienia są przewidywalne.

6

Cały ekosystem crates.io

serde, nalgebra, tracing, axum, WebRTC: węzeł ROS 2, API webowe i strumień telemetrii w jednym pliku binarnym, bez ani jednego find_package w CMake.

7

Wiadomości bez typów dla mostów

subscribe_untyped i create_publisher_untyped działają z JSON przez serde, więc generyczne mosty, rejestratory i narzędzia debugujące nie potrzebują typów wiadomości w czasie kompilacji.

8

Nowoczesne narzędzia i wdrażanie

clippy, rustfmt, cargo test i powtarzalne buildy z Cargo.lock w CI. Wdrażasz jeden samodzielny plik binarny w lekkim kontenerze zamiast całego drzewa install/.

Jak to wygląda

Kompletny węzeł: subskrypcja stanów stawów i publikowanie komend prędkości, wszystko w runtime Tokio. Porównaj to z analogicznym boilerplate w rclcpp.

# 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(())
}

Uczciwie: kompromisy

  • r2r jest rozwijane przez społeczność i nie należy do oficjalnych bibliotek klienckich ROS 2, więc tutoriali i odpowiedzi jest mniej niż dla rclcpp czy rclpy.
  • Budowanie wymaga załadowanej instalacji ROS 2, bo wiązania są generowane z pakietów wiadomości, które znajdzie.
  • Pierwszy build jest wolny, bo bindgen i generowanie wiadomości działają dla każdego pakietu. Buildy przyrostowe są w porządku.
  • Pliki launch i workspace colcon potrzebują odrobiny kleju (np. colcon-ros-cargo), by traktować crate cargo jak pakiet ROS.

Werdykt

Dla nowego middleware, mostów, teleoperacji i potoków sterowania r2r to lepszy wybór domyślny: asynchroniczność, która naprawdę pasuje do I/O w robotyce, bezpieczeństwo w czasie kompilacji i build, który jest po prostu cargo. rclcpp zostaw dla kodu legacy i sterowników producentów; po r2r sięgaj przy wszystkim nowym.