Every year since 2016, Rust has been surveyed as the most loved programming language on Stack Overflow. Although a relatively young language, numerous companies such as Mozilla, Discord, and Dropbox have all adopted it into their critical infrastructure. At RustConf 2020, Google Engineers mentioned that they use Rust in their Fuchsia operating system, OpenTitan RoT project, and a number of internal projects. The fact that companies are increasingly adopting Rust into their stacks shows that it has the potential to be a major player in the upcoming years. The hype is intriguing, so naturally I dived headfirst into the Rust Language Book by Steve Klabnik and Carol Nichols to see what all the fuss is about.
tl;dr Rust is AWESOME!
Rust is a system language built from the ground up with performance, security, and concurrency safety in mind. The Rust team follows the ideology to Eliminate things that don't need to be hard. This philosophy is exercised in Rust's build system Cargo. Cargo handles new project (a.k.a crate) creation, project checking (verifying that a crate will compile without explicitly building), compiling, unit testing, generating documentation, and even publishing to crates.io, all out of the box! Need a dependency such as a library? Simply list it in an auto-generated Cargo.toml file and the dependency will be fetched automatically when building. The compiler is more than a tool that spits out an executable binary; it will intelligently report errors and suggest possible solutions.
error[E0425]: cannot find value `readings` in this scope --> src/main.rs:21:24 | 21 | let lidar_distance = readings; | ^^^^^^^^ help: you might have meant to use the available field: `self.readings` error: aborting due to previous error For more information about this error, try `rustc --explain E0425`. error: could not compile `neato_xv11`. To learn more, run the command again with --verbose.
In this example, I forgot to add self before referencing a struct field in a method. The compiler caught it and gave me the right suggestion!
The beauty of Rust isn't what unique features it has, it's the fact that most of its features already exist in other languages. Niko Matsakis stated at RustConf 2020, "...and so that began this really cool blend that I've think we've seen till this day of programmers from a bunch of different backgrounds, coming together, to work in the same language and in the same community and bringing these different experiences". Rust technically is a system language, but the Rust team deliberately excluded the term from their slogan. On the contrary, their new slogan "A language empowering everyone to build reliable and efficient software." ignites the curiosity of programmers from all domains. They chose this slogan upon realizing that developers from other communities can also benefit from Rust. These communities can utilize a system language for performance boosts, while avoiding the costs of maintaining C/C++ software. Likewise, Rust can adopt solutions created by other frameworks. For example, Cargo was inspired from RubyGems and npm, compiler error messages were inspired from (or developed in competition against) Elm, and many influences came from existing languages. By adopting the wisdom of knowing what "works" and what doesn't, Rust can avoid the mistakes that older languages have made.
There are a few things that could use improvement. For starters, there aren't many IDEs, but there are plugins that work with JetBrains' IntelliJ and Microsoft's Visual Studio Code. Since most of my development has been on a Raspberry Pi, I exclusively used Vim. There is a cool open source IDE compiler named rust-analyzer (currently experimental), so it's safe to expect more IDEs in the future. Another feature that's needed is the ability to debug your application. Thankfully, you can unit test to find bugs in the meantime.
There is a significant learning curve when coming from another programming language. One reason is Rust's unique features such as ownership, borrowing, and lifetimes. Since there is no garbage collection nor does it require the user to free memory, Rust utilizes these features to ensure that memory is properly de-allocated. Thankfully, if you're struggling with the nuances of the language, there is a supportive community willing to help.
The Rust Community
Rust has the most inclusive community from any language or framework that I have ever worked with. Both the official and unofficial Discord treated me with respect and happily cleared up my confusions. This comes as no surprise because Rust has a Code of Conduct that has existed since the inception of the language. The original CoC author, Graydon, detailed his experiences dealing with toxicity from the language building community in a Reddit thread. He stated that the community was "so aggressive, so abusive, so full of flaming and trolling and insults and generally awful behaviour [sic], that they had given up even participating". By having a CoC that states unacceptable behavior, Rust can avoid dealing with the polarization of the internet and instead foster a empowering environment for all developers.
While attending CSU Channel Islands for my B.S. in Computer Science, I participated in an independent research program lead by Dr. Andrzej Bieszczad. ciNeuroBot is an ambitious project that aims to use Dr. Bieszczad's Neurosolver algorithm to help a mobile robot explore a maze autonomously.
ciNeurobot consists of:
- A Neato-XV11 LIDAR
- A Raspberry Pi 4
- A PiBorg chassis and driver motors
- 8 AA batteries
- A slot for an optional Raspberry Pi compatible camera
My duties included interfacing a Neato-XV11 LIDAR on a Raspberry Pi's UART port, creating a circuit to turn the LIDAR motor on/off using the Raspberry Pi's GPIO, and refactoring existing ciNeuroBot code to support a LIDAR simulator. I also performed tests on Dr. Bieszczad's Inverse Compensation Vector (ICV), a lightweight obstacle avoidance algorithm that uses LIDAR readings to generate a correction vector. The vector's direction points away from the closest obstacle and its magnitude grows exponentially higher depending on distance between the robot and said obstacle. The correction vector then gets added to the driver vector (The vector that moves the robot forward) which guides it away from the obstacle.
Figure 1: ICV when the robot is next to a corridor wall
Figure 2: ICV when the robot is in the middle of a corridor
In Figure 1, the robot is so close to the wall that the LIDAR fails to read the distance on the closest points (hence the gap), nevertheless it generates an ICV with a strong magnitude using 3 sample points. In Figure 2, however, the robot is distant from both walls so the ICV is insignificant (I'm intentionally leaving out a lot of details since it isn't the focus of this post. If you're interested, check out Dr. Bieszczad's paper).
Since I have a wealth of experience with the Neato-XV11 LIDAR, I figured a good starting project would be to translate my Python driver to Rust.
Writing the Rust Driver
Translating the driver was overall a smooth process with a few hiccups. The serial crate did all the heavy lifting of communicating with the Raspberry Pi's serial0 port, so all my application had to do is parse the data and verify its integrity. Since Rust is a statically typed language, it was a challenge shifting my mindset of relying on Python's dynamic typing. For example, every method that I had defined in my Python driver did not annotate the datatype, so I had to revisit these methods and determine the size of the variables. I felt that this gave me a deeper understanding of memory usage and allowed me to write some nice optimizations that I otherwise wouldn't have thought of when using Python.
Upon completion, I needed to test my new Rust driver. I confined my robot within four walls, ran the driver, recorded the distances reported, and copied it over to a Matplotlib polar coordinate graph. It worked like a charm!
Figure 3: Neato XV-11 LIDAR distances on a polar coordinate graph
Since the mobile robot is battery powered and uses a Raspberry Pi as its processor, performance is everything. RPis get stronger with each release, but they are still no match for most modern CPU/GPUs in terms of performance (and why would it? It's supposed to be a lightweight computer). Due to this, I am conscious to the RPi's computational limitations and power consumption. Upon completing my Rust driver, I decided to run a test to compare it to my existing Python driver. I ran each driver for about 20 minutes and recorded the load average using htop.
Figure 4: Load when using the Python driver
Figure 5: Load when using the Rust driver
Wow, that's pretty significant! It came as no surprise that the Rust driver would perform better, but what shocked me was how much better it performed (In the defense of Python, I did optimize the Rust driver a bit). By rewriting the LIDAR driver in Rust, I will have more freedom to write robot code without worrying that I'm overloading the CPU.
Now that I have a working LIDAR driver in Rust, I want to create my own ROS package so I can send LIDAR readings to other ROS modules. Two ROS client libraries that I'm looking at are rosrust and ros2_rust. I would like to thank the RustConf 2020 organizers and presenters for making it accessible to struggling developers during this COVID-19 pandemic. Be sure to check out the full livestream if you haven't already!