r2r is an easy to use, runtime-agnostic, async rust bindings for ROS2. r2r can be built using colcon but do not require hooking into the ROS2 build infrastructure if you do not need to. cargo build is all you need.
This library differs a bit in style from rclpy and rclcpp, as it eliminates all synchronous callbacks in favor for rust futures and streams. Coupled with the rust await syntax, this makes it very pleasant to work with ROS services and actions. The library purposefully does not choose an async runtime – this means that any runtime, such as tokio or async-std can be used.
Some simple examples
// subscriber
let sub = node.subscribe::<r2r::std_msgs::msg::String>("/topic")?;
sub.for_each(|msg| {
println!("topic: new msg: {}", msg.data);
future::ready(())
}).await;
// Publisher
let mut timer = node.create_wall_timer(duration)?;
let publisher = node.create_publisher::<r2r::std_msgs::msg::String>("/topic")?;
for _ in 1..10 {
timer.tick().await?;
let msg = r2r::std_msgs::msg::String{data: "hello from r2r".to_string() };
publisher.publish(&msg)?;
}
// Service client
use r2r::example_interfaces::srv::AddTwoInts;
let client = node.create_client::<AddTwoInts::Service>("/add_two_ints")?;
let mut timer = node.create_wall_timer(duration)?;
println!("waiting for service...");
node.is_available(&client)?.await?;
println!("service available.");
for i in 1..10 {
let req = AddTwoInts::Request { a: i , b: 5 };
if let Ok(resp) = client.request(&req)?.await {
println!("{}", resp.sum);
}
timer.tick().await?;
}
See more examples in the repository
What works?
Up to date with ROS2 Foxy, Galactic and Rolling
Building Rust types
Publish/subscribe
Services
Actions
Logging
Rudimentary parameter handling
TODO
Complete the services associated with updating parameters at run-time.
Would you be interested in collaborating with the ros2-rust project? The aims may be a bit different, ros2-rust’s goal is becoming part of the supported languages for ROS2 (hence the colcon integration and matching the ROS2 API), but I like the way r2r handles message generation, for example. ros2-rust follows the same pattern as the other language bindings for generating messages (empy templates, Python, etc.), but it might make sense to ditch that for ros2-rust in favor of the mechanism that r2r uses.
Anyway, it’d be cool to join efforts, or at least share as much code as possible.
Sorry for the off-topic, but for message generation in the Ada client library I ditched the idea of using the existing templates and did something similar to what r2r does. I found it quite more maintainable as a newcomer to ROS2 internals, and the introspection facilities are really fantastic.
We started using ros2-rust but at the time, when Dashing came, it was a lot of work to update to (and for us to understand) the new IDL-pipeline and colcon. We also wanted to be able to just use cargo run for simple rust nodes, so then we ended up binding to the already built c-messages instead. So, we have had a similar experience as @mosteo.
During the last year we have also focused on async support in all parts of r2r since we need it for our tools.
Yes, it would be great to collaborate and make one rust client, or anyway share experience. Maybe we can plan for a meeting to discuss this?
I also think that it makes sense to avoid the “standard” way of generating messages, but maybe that should be more of a ROS2 discussion for all languages? If for example all languages binds to the same c-messages, maybe the colcon build process will be faster? Or is that not possible? For me it has also been quite hard to fully understand the message generation for cpp and py.
I think it’s reasonable to avoid generating your own code if you can and remain performant. C++ needs it’s own (rather than wrapping C) so we can use std containers like std string and std vector without incurring additional copies. Also because meta programming with C++ is a bit challenging at the moment.
It might be possible to use things like string view and array view and/or class meta programming in the future to get rid of custom C++ messages, but for now it’s needed.
For python, we might be able to move away from generated code because the PyObject to C struct conversion happens anyway, but I think the generated c code helps with integration with Numpy but I could be wrong.
If you can just bind dynamically to the generated c structs we provide, without code generation that’s a good solution. Again so long as you can do so while minimizing or avoiding copies.
I created rclswi, a bridge to SWI-Prolog using the type introspection code to access the C structs. Getting started was a bit hard and required some help from this list. It now works well though. I did some timing. The time required to do the type introspection is neglectable while the interface performs considerably better than rclpy.
The only limitation I found that it is not possible to get access to the enum values associated with a type. This was unacceptable. In the end I added code to find and parse the .msg file to figure out the enums. Would be great if the core C library would provide access to the enums.
Hi,
I just posted ROS 2 support for Rust on embedded
Sorry, I missed this new rust ROS 2 library release. Would the C-API of rclc be interesting for you especially on embedded devices?
Yes that would be interesting. Rust is growing in embedded development and it would be really interesting to see if it is possible to also include async-await. There are some interesting runtimes for embedded async rust: e.g. GitHub - embassy-rs/embassy: Rust Embedded async executor and HALs
Not sure we have the time now to do this now, but we will talk about it here at Chalmers. Our main focus right now is to release our control / planning framework Sequence Planner · GitHub.
Would you be interested in using rclc as Rust interface to ROS 2?
That’d be an interesting approach, but the reason we created the rmw/rcl/client_libraries layers in ROS2 is so that client libraries would have the freedom to choose whatever threading and memory models feel more natural to languages. For example, with Rust we can control the life cycle of objects to ensure there aren’t memory issues.
That’d be an interesting approach, but the reason we created the rmw/rcl/client_libraries layers in ROS2 is so that client libraries would have the freedom to choose whatever threading and memory models feel more natural to languages. For example, with Rust we can control the life cycle of objects to ensure there aren’t memory issues.
Fully agree. Rust binding uses rcl as interface. rclc is a client_library for C, yes, but it is more a set of convenience functions ontop of rcl plus a static-memory executor. It might just make the Rust binding easier. But as the latest release supports services, actions and basic parameters, maybe the benefits of using rclc are not as large any more. On top of that, rclc provides parameters and lifecycle as well as a flexible executor, especially useful for real-time and deterministic embedded applications (priority assignment, trigger, user-defined execution sequence)
I think that to be able to use r2r on embedded devices, most work will be to change to no_std. Maybe rclc can simplify this? But maybe it is enough to continue use rcl since we anyway would like to use an async runtime.
Yes, we had a meeting last week. The plan is to see if we can migrate the message generation pipeline from r2r into ros2_rust as a start. We will also take a look at the rus2_rust colcon-cargo integration for r2r.