Tomasz Czajka

Robótica · Async Rust

Por qué r2r supera al stack clásico de ROS 2

ROS 2 es la columna vertebral de la robótica moderna, pero la forma en que la mayoría escribimos nodos, con rclcpp o rclpy compilados con colcon, no ha envejecido bien. r2r ofrece async Rust idiomático sobre el mismo núcleo de ROS 2, y cuando lo pruebas, cuesta volver atrás.

Rust r2r ROS 2 Tokio DDS
Robot humanoide Atlas de pie, vista frontal
Robot humanoide Atlas. Foto: DARPA, dominio público.

Primero, qué es realmente r2r

r2r no sustituye a ROS 2 en sí. Es una biblioteca cliente de Rust que se enlaza directamente con rcl, la misma capa en C sobre la que se apoyan rclcpp y rclpy, así que tus nodos siguen hablando con cualquier otro nodo de ROS 2 a través de DDS. Lo que cambia es todo lo que está por encima: el lenguaje, el modelo de concurrencia y las herramientas de compilación. La comparación, por tanto, es entre r2r y la forma estándar de escribir nodos de ROS 2: C++ o Python, compilados con colcon.

8 ventajas de r2r

1

Asíncrono por diseño

Las suscripciones son Streams; las llamadas a servicios y los objetivos de acciones son Futures. Los combinas con tokio::select!, timeouts y canales en lugar de lidiar con executors, grupos de callbacks y estado compartido entre callbacks.

2

Solo cargo, sin colcon

Los bindings de mensajes se generan al compilar a partir del entorno de ROS 2 cargado. Sin package.xml, sin CMakeLists, sin ament: cargo build, cargo test y rust-analyzer simplemente funcionan.

3

Seguridad de memoria y de hilos

El compilador descarta condiciones de carrera, use-after-free y punteros colgantes en los callbacks, las causas clásicas de que fallen los nodos en C++. En un humanoide que camina, eso importa.

4

El mismo núcleo de ROS 2 debajo

Como r2r envuelve rcl, funciona con cualquier implementación RMW (Fast DDS, Cyclone DDS, Zenoh) e interopera con nodos existentes en C++ y Python: topics, servicios, acciones, parámetros y relojes.

5

Velocidad nativa, sin GIL

Rust compilado con abstracciones de coste cero y sin recolector de basura. A diferencia de rclpy, no hay un Global Interpreter Lock que frene los topics de alta frecuencia, así que la latencia es predecible.

6

Todo el ecosistema de crates.io

serde, nalgebra, tracing, axum, WebRTC: un nodo de ROS 2, una API web y un stream de telemetría en un único binario, sin un solo find_package de CMake.

7

Mensajes sin tipo para puentes

subscribe_untyped y create_publisher_untyped trabajan con JSON mediante serde, de modo que los puentes genéricos, grabadores y herramientas de depuración no necesitan tipos de mensaje en tiempo de compilación.

8

Herramientas y despliegue modernos

clippy, rustfmt, cargo test y builds reproducibles con Cargo.lock en CI. Despliega un único binario autónomo en un contenedor ligero en lugar de todo un árbol install/.

Así se ve

Un nodo completo: suscripción a los estados de las articulaciones y publicación de comandos de velocidad, todo dentro de un runtime de Tokio. Compáralo con el boilerplate equivalente en 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(())
}

Para ser justos: las contrapartidas

  • r2r lo mantiene la comunidad y no es una de las bibliotecas cliente oficiales de ROS 2, así que hay menos tutoriales y respuestas que para rclcpp o rclpy.
  • Compilar requiere una instalación de ROS 2 cargada, ya que los bindings se generan a partir de los paquetes de mensajes que encuentra.
  • La primera compilación es lenta, porque bindgen y la generación de mensajes se ejecutan para cada paquete. Las compilaciones incrementales van bien.
  • Los archivos launch y los workspaces de colcon necesitan algo de pegamento (por ejemplo colcon-ros-cargo) para tratar un crate de cargo como un paquete de ROS.

Veredicto

Para middleware nuevo, puentes, teleoperación y pipelines de control, r2r es la mejor opción por defecto: asincronía que de verdad encaja con la E/S robótica, seguridad en tiempo de compilación y un build que es solo cargo. Deja rclcpp para el código heredado y los drivers de fabricantes; usa r2r para todo lo nuevo.