At Sonair, we’re not just building next-generation sensor systems; we’re reshaping how autonomous robots perceive and interact with the world. Our commitment to leveraging the latest technologies extends beyond the hardware—it’s embedded in every line of code we write. Enter Rust, a programming language that has rapidly gained traction for its speed, safety, and power.
Why Rust?
In this deep-dive article, we’ll take you behind the scenes of our development journey, sharing how we’re using Rust to accelerate Sonair’s innovation, tackle complex challenges, and set a new standard for functional safety in embedded systems.
Let’s dive in.
The challenges we face
A developer of advanced sensor systems for robotics like Sonair faces typical business challenges. They include identifying the problem you want to solve, understanding the market, and balancing the need for speed to market with creating a solution that has a lasting impact.
We need to make decisions on hardware to run on, determine how to implement the algorithms, and choose the right programming language for implementation.
Some languages are great for prototyping, but less agile in a production environment, while others are highly efficient, but require a clear understanding of the end goal. There is also the existing team’s competence, the state of the recruitment market, and the ease of finding people with the required skills. All these factors and trade-offs need to be carefully evaluated.
When we started Sonair, I knew we would have a team of experienced embedded firmware developers with a background in C/C++. That’s the environment I came from myself. While C/C++ are strong and capable languages, they are also burdened by legacy. They were conceived many years ago and have evolved over time, not always for the better.
The main reasons for using C/C++ are their speed, flexibility, wide market acceptance, tool support and, to some extent, existing vendor code bases. They also have a strong backward compatibility, which ensures that they keep running seamlessly.
Programming languages have evolved, and Rust is emerging as a response to this evolution. It builds on the lessons learned from low-level languages, taking the best aspects while striving to avoid legacy issues such as manual memory management, complex error handling, and complex build systems. This was a key factor for Sonair when we chose Rust as our core programming language. We wanted a language that could run easily across different platforms, was incredibly fast and efficient, and could perform well on low-capability hardware. Our goal was to run on small cores rather than a multi-core PC setup.
We wanted a language that could run easily across different platforms, was incredibly fast and efficient, and could perform well on low-capability hardware.
At the same time, we were receiving input from the broader industry ecosystem. The CTO of Microsoft announced that no new programs at Microsoft would be developed in non-safe languages, with Rust specifically mentioned as a preferred language. A few months ago, U.S. authorities declared that writing software using unsafe languages could be considered a security risk. By incorporating memory safety into programming languages and embedded systems, developers can build robust, secure, and reliable systems, particularly in environments where failures can have serious consequences. (Memory safety in computing refers to the assurance that a program will only access memory it is allowed to, and that it avoids common issues related to memory mismanagement.)
Why we chose Rust
Deciding to use Rust was challenging because no one on the team had prior experience with it. However, we had extensive experience with C and C++, and what ultimately happened was simply deciding, “Okay, let’s try Rust and see how it feels.” Almost immediately, we were blown away. Rust pushed all the right buttons and, despite an initial learning curve, felt incredibly comfortable to work with. In the end, it became an easy choice.
The most important reason for choosing Rust is that it combines the best parts of low-level languages we love with the high-level features we appreciate in languages like Python. We value the ability to write high-performance code that operates close to the hardware while maintaining full control over execution—just like we would with low-level languages. At the same time, Rust’s type system, along with its opinionated borrow checker and enforced memory constraints, pushes you toward a design that forces you to make the right decisions from the start. The compiler errors and warnings are also clear, understandable and helpful, making the transition much easier.
“Okay, let’s try Rust and see how it feels.” Almost immediately, we were blown away.
What really won us over though (to the point where going back to C/C++ would have felt incredibly painful), was the ecosystem. We have a standing joke in the office: “Of course there is a crate for that.” Crates are open-source libraries that you can add to your application with a simple “cargo install.” They are all centrally hosted, making them easy to find. It could be a (powerful) crate for serializing messages before sending them over UART (postcard), a driver for an I2C temperature sensor, a crate with ready-to-use types for frequency, duration and so forth, with functions for converting between them. Or an incredibly clever logging crate that can dramatically reduce the footprint of an application by using “deferred formatting.” All crates are documented in a standardized way, making them easy to use.
Then there are peripheral access crates, which are usually auto generated, with documentation, from vendors’ SVD files (a standardized file that describes a device with all its registers). Combined with IntelliSense, they make working with registers a breeze. Something like tim.cr1.modify(|_, w| w.urs().counter_only())
modifies the cr1
register of a timer so that the update request source is counter only. (Yes, you still need to read the manual). This is done with zero-cost abstraction, so the assembly code is the same as you would get from a hand-coded c-file.
We have a standing joke in the office: “Of course there is a crate for that.”
Finally, you have HAL (hardware abstraction layers) readily available for many chips (especially ST Microelectronics and Nordic Semiconductors). They are not complete but have enough functionality to hit the ground running. They also use Rust’s type system in clever ways to prevent you from shooting yourself in the foot.
How we’re using Rust
Obviously, there are challenges. Some concepts in Rust require learning, especially if you haven’t encountered them before. For embedded systems, for instance, everything is typically just a location in memory. Whether it’s an input line, an ADC, or something else, from the perspective of a C compiler, it’s simply an address. It’s entirely up to the developer to handle this memory location appropriately—whether it represents an ADC, an output, or an input.
With Rust, however, you can create a type system that enforces correct usage. A well-designed type system can, for example, ensure that you treat an output line differently from an input line. If you try to set an input pin high, you won’t get a runtime error—you’ll get a compile-time error. Also, it can ensure that your pin is configured correctly before you can use it for SPI communication, and if you try to use a pin both for UART and SPI at the same time, you get an error. This enforcement happens entirely at compile time, so there’s no performance cost when the code is running. It’s all checked beforehand, which is just beautiful.
For Sonair, it’s Python for prototyping algorithms and Rust for production.
Granted, in C++ you can achieve almost everything Rust offers, but C++ is a large and complex language, and you must choose to use it in a safe manner – with Rust the compiler will enforce safe usage. Even as an experienced embedded developer, it’s easy to make mistakes because there are subtleties where your assumptions could be proven wrong. Rust handles this better.
We’re still using Python for prototyping and experimentation to outline the algorithms because it’s very easy to iterate in that language. We can also leverage Python’s vast ecosystem of packages. Once the algorithms start to solidify, our process is to convert them to Rust, as we need to get them running on the target system. In Python, we typically run the code on the host computer, which could be a powerful PC running Linux or Windows. Once that’s done, we convert the Python code to Rust and begin running it on the target. At that point, we can run the code on either the host or the target, which is another benefit of Rust—it allows for easy cross-compilation to both platforms. Developing in Rust doesn’t prevent us from interacting with previously developed software packages in C/C++.
The mathematical implementations of ARM and the CMSIS DSP (Digital Signal Processing) are something we don’t want to reimplement. It’s very easy to write a wrapper in Rust that allows us to call the underlying C implementation.
Recruiting
Since Rust is still much less used than e.g. C/C++, there are fewer developers with Rust experience. On the flip side, those that are available are typically developers with an above average interest in programming languages, and from our experience, talented. What we’ve discovered is that when we say, “Hey, we’re a Rust shop and we need people,” it attracts talent, because guess what, there are few Rust developers, but even fewer Rust jobs. As a result, we’ve been very successful in bringing engineers on board and making them productive. For hires not familiar with Rust, our experience has been that the similarities between Rust and other languages are so significant that onboarding has not been a challenge.
The pleasurable pain points of Rust
There are definitely aspects of Rust that can be painful, and you need to work around them. Usually, they’re painful because they address problems that you might not even realize you have in C/C++. Rust forces you to confront these issues. Our experience is that the pain comes from being required to address something that you might have been able to avoid in other languages, where you could have waited to deal with it until it became necessary.
From our experience, the moment you really have to deal with these kinds of problems is when it’s too late – you should have addressed them upfront. We find that Rust’s design decisions help us avoid making those early assumptions and mistakes.
Rust forced us to write correct code.
The lessons learned for Sonair are that we don’t believe we could have developed the Sonair 3D ultrasonic evaluation kit at the speed we did without Rust. Rust helped us and forced us to write correct code. Rust is open source and encourages the publishing of packages and implementations. The ecosystem has accomplished what the Python world has done with its Pip environment, and what the C/C++ communities never fully succeeded in sharing code. This has truly accelerated Sonair’s product development timeline.
Safety-certified systems with Rust?
One difficult pain point is certifications. Sonair is developing a safety-certified device (IEC 61508 and SIL2). It’s a paradox for us that everyone agrees Rust is a safer language, libcore is not yet certified, so building a certified Rust app is still not trivial. Sonair is on a mission to successfully release safety-certified Rust code.
It still feels like a hurdle. For sure, the opinionated aspects of Rust sometimes make us feel it would be easier to do things in C. We do struggle with this at times. The nice thing about Rust is that, if necessary, you can always fall back to a different language. It’s unsafe, and you’re totally on your own—you need to take full responsibility for the choices you make. You’re pushed into a corner where you might say, “Okay, I know that Rust is complaining, but I really know what I’m doing here, so I’m going to proceed because I’m confident it’s safe.” This forces you to take a step back and carefully consider your decisions.
Making Rust a strong player
Our advice to others is that Rust is very suitable for low-level code, especially if you’re writing high-level code. If you’re working on prototypes, Rust, for better or worse, is a strongly typed language. This means you need to put more effort into specifying your classes, structs, and interfaces. If you’re really in an exploratory phase, we still think Python has many strong points. Rust is picking up speed, and with the first iteration, you have a shorter path to getting it running on the target.
For Sonair, it’s Python for prototyping algorithms and Rust for production. We feel this gives us the best of both worlds. In the future, we hope more people will develop functional safety in Rust, so the ecosystem will evolve with certified, third-party crates, making it easier to get started.
Right now, you’re quite limited in what you can trust, so you need to do a lot on your own. A safety-certified Rust implementation is much harder than a non-safety-certified version. We believe that in a couple of years, this will change. Perhaps Sonair is a couple of years early, but we’re confident we’re on the right track, and we hope to contribute to the community. We want to move forward and succeed in making Rust a strong player in both embedded systems and safety-certified systems.
Reach out to the Sonair team at hello@sonair.com
Join our Early Access Program
Further reading:
- An introductory book about using the Rust Programming Language on “Bare Metal” embedded systems. The Embedded Rust Book
- How Rust went from a side project to the world’s most-loved programming language MIT Technology Review, Feb 14, 2023
- Ferrocene is the open-source qualified Rust compiler toolchain for safety- and mission-critical. Ferrocene.