Skip to content

How to Start with Rust in ROS 2 Based Robotics Systems?

Rust is a modern programming language intended for high-performance systems. Let’s check if it makes sense to use it in robotics systems based on the ROS 2 framework.

Content

  1. Introduction to ROS2
  2. Why Rust?
  3. Example of Rust package in ROS 2
  4. Conclusion

Introduction to ROS2

ROS2 (Robot Operating System) is a second version of the most popular robotics open-source software framework. It allows to divide complex systems into smaller parts, called nodes. The nodes communicate with each other with a few mechanisms, like topics, services, or actions. The ROS provides also multiple tools for debugging, packages managing and binaries launching.

Topics – a publish-subscribe pattern

It is a basic messaging pattern used in ROS. According to it, nodes can take the role of publishers or subscribers, or both at once. The publisher (sender of message) sends a specified type (class) of the message on the specified channel (topic). On the other hand, a subscriber expresses its interest in a specific type of message on the specified topic. Then, it receives only messages that are sent to this topic. To sum up, in the publish-subscribe messaging pattern, publishers don’t send messages to specific subscribers but to the topic which can be observed by subscribers.

Why Rust?

Rust is a modern programming language that is becoming more and more popular (StackOverflow Survey). And there are few reasons for that Firstly, it has easy, intuitive syntax. Simultaneously, it’s a high-performance language with an emphasis on safety.

Main features

  • Compiled language, created by Mozilla.
  • Memory safety, no garbage collection.
  • Statically and strongly typed. Static typing means that types are known at compile-time. On the other hand, strong typing forbids implicit conversions between unrelated types.
  • High-performance, general purpose, and multi-paradigm language.
  • Applications from embedded systems to graphical interfaces.

Comparison of Rust with C++

Why compare Rust with C++? Because both are high-performance languages and are intended for similar applications.

Similarities

  • Both are open-source compiled languages.
  • One and the other are high-performance languages and their efficiency is comparable in benchmarks (for example this benchmark).
  • Both are intended to use with a minimal set of runtime dependencies.
  • As mentioned, they are intended for similar applications, for example embedded or IoT systems.
  • Both allow to easy use of C code (C++ code also can be called from Rust but it’s not so easy and convenient).
  • Rust and C++ are statically typed, so all types have to be known at compile-time.

Differences

Easy of use
  • I know it can be subjective but I think that Rust is easier to use. It has better library integration and dependencies management system. Dependencies management is not convenient in C++ and probably each C++ programmer found out about it. It’s especially annoying in smaller, short-term projects.
  • Rust has a very usable and easy-to-use tool for the project management Cargo. In comparison, C++ projects can use make which is very powerful but also harder to learn and use.
  • Rust has more convenient, modern syntax (of course it’s rather a subjective opinion but not the only one). In recent years C++ has been significantly changed (simplified and extended with new features).
  • The documentation of Rust is really good. But in my opinion, C++ documentation is also great, especially since that language has its years.
  • Probably C++ will be a better choice for GUI applications because of more tools. But still Rust can be used for example with Qt library.
Abstractions
  • Rust has traits to handle abstractions. They’re used for both Run-time polymorphism and Compile-time polymorphism.
  • On the other hand, the C++ has multiple mechanisms for abstractions. These mechanisms are abstract classes, templates but also concepts, introduced in C++20.
Safety
  • Unsafe code in Rust is always written intentionally, or at least it has to be marked as unsafe.
  • Safe code in C++ needs more attention. There are multiple protections for that but are not enabled by default.
  • Rust is strongly typed while C++ is weakly typed. So, in Rust, implicit conversions between unrelated types are not allowed.

Example of Rust package in ROS 2

Rust support in ROS 2

Rust is not supported in ROS 2 by default. However, there are two solutions to provide such a support.

I have used the first one (ros2-rust) package. Mostly, because it’s more actively developed and in my opinion has a better documentation. So, the presented example will be based on this approach.

Prerequisite

Testing Environment

I have used the following specification. However, newer versions should be also fine, especially Ubuntu 22.04 and ROS 2 Humble.

Used software:

  • Ubuntu 20.04
  • Python 3.9
  • rustc 1.64.0 (Rust compiler)
  • ROS 2 Galactic

Environment preparation

  • Install Python 3.8+
  • Install ROS 2 Galactic (or Humble). Here is the instruction for Ubuntu.
  • Install Rust (the script is intended for Linux).
    curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
  • If you have already installed the Rust, then just update it:
    rustup update
  • Install tools necessary to configure and build the ros2-rust ROS package
    sudo apt install -y git libclang-dev python3-vcstool python3-pip
  • Install packages necessary to use Cargo with ROS packaging system colcon
    cargo install cargo-ament-build
    pip install git+https://github.com/colcon/colcon-cargo.git
    pip install git+https://github.com/colcon/colcon-ros-cargo.git

Workspace creation

  • Create a new workspace
    mkdir -p ~/ros2_rust_ws/src
    cd ~/ros2_rust_ws/src

Build ros2_rust from sources

  • Clone the ros2-rust repository to the workspace

    cd ~/ros2_rust_ws/src
    git clone git@github.com:ros2-rust/ros2_rust.git
  • Get all ros2-rust dependencies and source ROS configuration. Make sure that a ROS_DISTRO is set to the correct ROS version (currently ros2_rust supports foxy, galactic, humble and rolling)

    cd ..
    vcs import src < src/ros2_rust/ros2_rust_${ROS_DISTRO}.repos
    . /opt/ros/${ROS_DISTRO}/setup.sh
  • Build the workspace with ros2-rust

    colcon build --packages-up-to examples_rclrs_minimal_pub_sub

    In case of building issues please check the original building instruction.

ROS package preparation

  • Create a new cargo package rust_example
    cd ~/ros2_rust_ws/src
    cargo new rust_example --bin --vcs none
  • Create package.xml file (it’s a part of each ROS package)

    <package format="3">
        <name>rust_example</name>
        <version>0.0.0</version>
        <description>Example package</description>
        <maintainer email="author@domain.com">user</maintainer>
        <license>License type</license>
    
        <build_depend>rclrs</build_depend>
        <build_depend>std_msgs</build_depend>
        <exec_depend>rclrs</exec_depend>
        <exec_depend>std_msgs</exec_depend>
    
        <export>
            <build_type>ament_cargo</build_type>
        </export>
    </package> 

The rclrs is a main package from ros2_rust intended for ROS. It’s equivalent to rclcpp for C++ in ROS.

  • Add dependencies to Cargo.toml

    [dependencies]
    anyhow = {version = "*"}
    
    [dependencies.rclrs]
    version = "0.3"
    
    [dependencies.rosidl_runtime_rs]
    version = "0.3"
    
    [dependencies.std_msgs]
    version = "*"
  • Compile a new package (empty for now)

    colcon build --packages-up-to rust_example

Create a publisher node

  • Rename automatically created src/main.rs to src/publisher.rs

  • Write code for the publisher node. It’s a very simple implementation that publishes a message with number in a loop.

    // Publisher main function
    fn main() -> Result<(), anyhow::Error> {
    
      let context = rclrs::Context::new(std::env::args())?;
    
      // Create a node and add publisher to it
      let node = rclrs::create_node(&context, "rust_publisher")?;
      let publisher =
          node.create_publisher::("topic", rclrs::QOS_PROFILE_DEFAULT)?;
    
      let mut counter: u32 = 1;
    
      while context.ok() {
          // Prepare a message
          let mut message = std_msgs::msg::String::default();
          message.data = format!("message {}", counter);
    
          // Publish the message
          publisher.publish(&message)?;
          println!("Published: {}", message.data);
    
          counter += 1;
          std::thread::sleep(std::time::Duration::from_secs(1));
      }
    
      Ok(())
    }
  • Add publisher binary definition to the Cargo.toml

    [[bin]]
    name = "publisher"
    path = "src/publisher.rs"
  • Build a rust publisher

    source install/setup.bash
    colcon build --packages-select rust_example

Verify if the publisher works correctly

  • Run the publisher node

    ros2 run rust_example publisher
  • Check if data is published correctly on the specified topic (/topic)

    ros2 topic echo /topic

It should show data like

  Published: message 1
  Published: message 2
  ...

Create a subscriber node

  • Create src/subscriber.rs file

  • Copy the subscriber code. It just receives data on specified topic and print it.

    // ROS2 subscriber
    
    fn callback(msg: std_msgs::msg::String) {
    println!("Received: {}", msg.data);
    }
    
    fn main() -> Result<(), anyhow::Error> {
      let context = rclrs::Context::new(std::env::args())?;
      let mut node = rclrs::create_node(&context, "rust_subscriber")?;
    
      println!("Waiting for messages...");
    
      let _subscription = node.create_subscription::(
          "topic",
          rclrs::QOS_PROFILE_DEFAULT,
          callback
      )?;
    
      rclrs::spin(&node)?;
      Ok(())
    }
  • Add to subscriber file to Cargo.toml

    [[bin]]
    name = "subscriber"
    path = "src/subscriber.rs"
  • Build the package again to make sure that subscriber code is fine

    colcon build --packages-select rust_example

Test of nodes communication by topic

  • Two terminal will be necessary. Run the publisher node in the first terminal

    ros2 run rust_example publisher
  • In the second terminal run the subscriber node

    ros2 run rust_example subscriber

You should see information about sent and received messages in terminals. If it works, launcher can be created optionally to simplify nodes running process.

Creation of a launch file (optional)

  • Create a new directory launch in the package.

  • Add launch directory to the installation list in Cargo.toml. Just copy below lines.

    [package.metadata.ros]
    install_to_share = ["launch"]
  • Create a launch file publisher_subscriber_launch.yaml. It has been used yaml version but ROS 2 supports also python or XML launchers.

    launch:
    
    # Publisher node
    - node: 
      pkg: 'rust_example'
      exec: 'publisher'
      output: 'screen'
    
    # Subscriber node
    - node:
      pkg: 'rust_example'
      exec: 'subscriber'
      output: 'screen'
  • Run the example with created launch file.

    ros2 launch rust_example publisher_and_subscriber_launch.yaml

You should see information about sent and received messages.

Repository with this example

Code of this example has been published as GitHub repository ros2_rust_example. So, alternatively it could be just cloned and compiled in created workspace with configured ros2_rust.

Conclusion

  • The simple example shows that Rust can be successfully used in ROS 2. Of course, the set of available ROS features is still limited and rclrs implementation is not as mature as the one for C++ (rclcpp) or Python (rclpy).

  • The potential drawback of using Rust at this stage is related to missing libraries. There are a lot of robotics (mrpt, bullet, moveit etc.), optimization (Ceres Solver, CasADi), or computer vision (opencv, pcl) libraries written in C++ or Python. I think sooner or later more Rust libraries or interfaces to existing software appears.

  • However, in my opinion, the Rust has a great potential in ROS environment. Basically, it provides a similar level of performance and the same scope of application as C++. But with simpler code and higher level of safety, that is easier to reach. I hope that at some point, the Rust support will be added to ROS 2 (ros_core) by default in future!

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.