In Part 1 of this blog series, we explored building bare-metal applications on Arm microcontrollers using Rust. Part 2 delved into integrating Rust with Real-Time Operating Systems (RTOS) on microcontrollers and medium-sized microprocessors. Now, in Part 3, we turn our attention to using Rust with full-blown operating systems like Linux, Windows, macOS, QNX, or Android on Arm processors.
On Arm, these systems are typically executing A64 instructions in AArch64 mode, on processors for example the Cortex-A76, found in the Raspberry Pi 5, or the Neoverse V2 found in the latest AWS Graviton cloud servers. Rust also has good support for 32-bit Arm systems for example the Cortex-A8 and the Arm11, even going as far back as the Arm7 from the 1990s.
Figure 1: Approaches to writing Rust applications
With an Application Processor, you generally have access to the full Rust Standard Library. This library abstracts many of the OS-specific interfaces, offering consistent APIs for threads, filesystems, networking, and more, regardless of the operating system. This means that developers can use their favorite platform for software development yet be confident that the same source code will compile for, say, their Linux-based production system.
To demonstrate the high-level expressivity of Rust, an example Rust application is shown in Figure 2.
fn main() -> Result<(), std::io::Error> { let contents = std::fs::read_to_string("input.txt")?; for line in contents.lines() { if let Some(payload) = line.strip_prefix("MESSAGE: ") { println!("Found payload: {payload}"); } } }
The code in Figure 1 reads a UTF-8 encoded text file into heap-allocated String, exiting cleanly if the file fails to open. Subsequently processing it line by line is straightforward in Rust thanks to built-in support for iterators - this example looks for lines that begin with "MESSAGE: " and prints the remainder of any matching line. This high-level API feels like Java or C#, but with performance of a C application – an advantage unique to Rust.
The Rust toolchain does not just include the compiler; it also includes a combined build system and package manager called cargo. This tool massively simplifies building Rust applications – a simple cargo build --release command is typically all that is required to build even the most complex project. As part of the build, cargo can download dependencies from third-party package repositories like crates.io, resolve semantic versions, and build up a complete dependency tree for your project – including all-important open-source licensing information.
The Rust compiler is also a cross-compiler out-of-the-box. This means that unlike with some C compilers, you do not need to go and install a specific version of the compiler for any given host or target combination. Instead, you can use rustup, the Rust toolchain manager, to download and install a suitable pre-compiled Rust Standard Library for your chosen target, and then you are off to the races. Figure 3 shows how you would use rustup to install support for cross-compiling to 32-bit Arm Linux running on the Armv7 architecture.
$ rustup target add armv7-unknown-linux-gnueabihf info: downloading component 'rust-std' for 'armv7-unknown-linux-gnueabi' info: installing component 'rust-std' for 'armv7-unknown-linux-gnueabi' 23.8 MiB/23.8MiB (100 %) 18.9 MiB/s in 1s ETA: 0s
Figure 3: Using rustup to add support for a new target.
The Rust project splits its supported targets into Tiers. Tier 1 is the highest tier, and any targets here will be compiled and tested on every Rust release. This tier includes 64-bit Arm Linux, along with x86 Linux, Windows and macOS.
A Tier 2 target is compiled, but the test suite has not been run. This tier includes the Armv7 Linux example above. Tier 3 targets are merely offered on a best-effort basis, and this is where the more exotic targets live – for example Rust on the Nintendo Switch, or Rust on Linux on Arm7. At the current time, Tier 3 targets are only supported using the ‘nightly’ Rust toolchain, and not the stable version. It is also worth noting that Rust, just like C and C++, requires a suitable linker for your target platform. For many targets, the bundled LLVM linker ‘lld’ will work, but in some cases, you may need to install a specific linker.
For anyone who needs support beyond what the standard Rust tier system offers, Ferrocene offers a solution. Ferrocene is commercially supported downstream of the Rust toolchain and is produced by Ferrous Systems. Arm and Ferrous Systems are working closely together to make specific hardware targets available in Ferrocene that might otherwise only be available as Tier 2 or Tier 3 in the upstream Rust project. Ferrocene targets pass the Rust Test Suite, and a subset of them are qualified by TÜV Süd for use with ISO 26262 ASIL-D and IEC 61508 SIL-4, with further industry-specific qualifications planned.
This blog series looked at three examples from across the broad Arm spectrum of devices and dug into the specifics of using Rust on that platform. We’ve seen that whether you’re looking to build on an existing fully-fledged Operating System, work with a Real-Time OS, or go bare-metal, Rust can help developers build high-performance, safe, and secure software. It offers features that help developers get to production more quickly than when using legacy languages. The type checking allows for the construction of APIs that are hard to use incorrectly, which means you are much more likely to use them correctly - saving valuable debugging time. The borrow checking means that buffer-overflows and use-after-free errors are effectively impossible in ‘safe’ Rust and you only have to check for such issues in the very much smaller set of ‘unsafe’ Rust code that our project might use to interact with the hardware or the OS. And the optimisations that come from using LLVM mean that Rust produces binaries that are on-par performance wise with C and C++, whether you on an Applications Processor, a Real-time System, or on a Microcontroller.
If you are looking for a Rust compiler with commercial support and optional functional-safety qualification, please check out Ferrocene from Arm partner Ferrous Systems. Ferrocene currently offers a ISO26262 ASIL-D and IEC61508 SIL-4 qualified compiler for bare-metal AArch64 targets, with additional 32-bit Arm Cortex-R and Cortex-M targets en-route to qualification. Figure 4: The Raspberry Pi 5 https://www.raspberrypi.com/documentation/computers/raspberry-pi.html