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.

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
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.
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.
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.
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.
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.
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.
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.
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 --releaseuse 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.