C and C++ have been the language of microcontrollers for decades, except for the odd hand-optimized assembler function. So what chance does Rust have, what benefits does it offer, and should you start learning this new language?

For decades, C has been the language of choice for embedded systems. Thanks to its ability to access and manipulate memory directly, it has been embraced as an alternative to assembler. C++ has also impacted smaller microcontrollers thanks to Arduino, encouraging an object-oriented approach to the design of libraries. Developers have benefited from a host of libraries that can be easily integrated and deployed, supporting USB applications, wireless protocols, and interaction with external sensors.

But these languages have their limitations. They are great at the very low level but, as a result, miss high-level support for things like decoding JSON or XML. Environments such as Arduino make sharing libraries simple; otherwise, there are no central repositories for C/C++ libraries with formalized application programming interfaces (API). And, as an embedded programmer, you'd never think to use the available memory allocation libraries or features such as printf.

Subscribe
Tag alert: Subscribe to the tag Rust and you will receive an e-mail as soon as a new item about it is published on our website!

Then there are all the fantastic mistakes you can make with pointers and uninitialized variables. Thanks to this linguistic capability, programmers can access any variable, function, or register unless the microcontroller's hardware hinders it due to security measures. And while this is a godsend at times, an incorrectly formed line of code can cause heaps of challenging problems.

What Is Rust?

Rust is a relatively new, general-purpose programming language developed by Graydon Hoare. The project started in 2006 during his time at Mozilla and later became a standalone project. It was designed for developing software for systems, such as DNS, browsers, and virtualization, and has also been used for a Tor server. Rust has several goals, but perhaps the most important is the baked-in approach to memory safety. 

For example, when initializing a pointer variable in C, it should be assigned NULL (technically known as a null pointer constant) if the actual value is not currently available. Functions using pointers should check for NULL before attempting to use a variable or function pointer. However, this is either not done, or programmers forget to add the check. There are also microcontrollers for whom 0 (zero), the value of NULL, is a valid memory or code location.
 
#include <stdio.h>
int main() {
   int *p= NULL;    //initialize the pointer as null.
   printf("The value of pointer is %u",p);
   return 0;
}

In Rust, NULL doesn't exist. Instead, an enum (enumerated type) exists that contains either a value or no value. Not only can it be used with pointers, but it can also be used in many other cases, such as a return value from a function that has nothing (rather than 0) to return. This is demonstrated in the following divide function that cleanly handles potential divide by zero situations. 
 
fn divide(numerator: f64, denominator: f64) -> Option<f64> {
    if denominator == 0.0 {
        None
    } else {
        Some(numerator / denominator)
    }
}

// The return value of the function is an option
let result = divide(2.0, 3.0);

// Pattern match to retrieve the value
match result {
    // The division was valid
    Some(x) => println!("Result: {x}"),
    // The division was invalid
    None    => println!("Cannot divide by 0"),
}

Code example from: https://doc.rust-lang.org/stable/std/option/index.html

In this divide function, if the denominator is 0.0, the return value None indicates that no answer can be provided. The code calling the function can check for a None return value or Some(T), i.e., a valid response value.

Type safety is also very strict, ensuring that data types of variables match those of other variables or literals that the programmer may try to assign to them. Thus, trying to implicitly add an integer to a string will be flagged as an error during compilation. Another goal of the language is concurrency. This is the ability for the compiler to change the order in which code is executed. For example, some code may comprise of an addition, a subtraction, and the cosine of the angle (in this order). Rust may reorder this calculation if the correct result can still be achieved. However, this capability targets multi-core and multi-processor systems and is less applicable to small embedded systems.

Subscribe
Tag alert: Subscribe to the tag Microcontrollers and you will receive an e-mail as soon as a new item about it is published on our website!

Can I Use Rust with My Microcontroller?

Theoretically, yes, but there are a few practical hurdles. The first is the toolchain. Most developers will have used gcc to compile their C code. However, Rust uses LLVM. Rather than being a compiler, LLVM is a framework for creating compilers. For example, using Clang together with LLVM, it is possible to compile C code for a microcontroller.  Many manufacturers have moved to an LLVM-based toolchain, especially those offering Arm Cortex processors. If there is no LLVM support for your preferred device, you're not going to be using Rust any time soon.
 
Slide1.PNG
With LLVM, various language-specific front ends generate LLVM IR output that is
then converted to the machine code of the target processor, e.g., Arm Cortex-M.
The next challenge is sourcing the code that provides access to the peripheral registers in Rust. This brings us to a discussion about crates.

While code is written in Rust, crates are what package everything up. A crate contains the source code and configuration files for your project. And, if you want the support package for a development board, like the micro:bit, you’ll want its crate. There is also a crate for the peripherals of the Nordic nRF51 series microcontroller on that board. Finally, there is a crate specifically for the Arm Cortex-M processor that powers it. 

Another cool thing about crates is that there is a lot of code already available for generic tasks, like implementing I2C, or interfacing with SPI sensors. Using the crate approach, you can list all the crates used in your project, and even note their version number so that others know which version worked when the project was created. Crates are usually stored in a central, online repository (crates.io), so they are easy to acquire.
 
Rust - generic dev board v4
With Rust, crates bundle access to processor registers, microcontroller peripheral registers,
and even resources like an I2C temperature sensor on your development board.

What Other Cool Stuff Is Built Into Rust?

Loads of other interesting extras are built into the Rust ecosystem, from the language to the tools.

Literals, the values we assign to variables and constants, are often difficult to decipher, especially as your vision starts deteriorating, either through age or due to the number of screen hours you've invested that day. Binary, hexadecimal, and even large integers and constants can unintentionally gain or lose a number or decimal place. Rust tackles this by allowing underscores to be used to break the value up into manageable chunks. The underscore is used purely to improve legibility and performs no other role. Type can also be defined using a suffix.
 
10000 => 10_000
0.00001 => 0.000_01
0xBEEFD00F => 0xBEEF_D00Fu32 (32-bit unsigned integer)
0b01001011 => 0b0100_1011

Further examples: https://doc.rust-lang.org/book/ch03-02-data-types.html

Interestingly, integers can overflow in Rust, but this behavior is monitored during compilation. Overflows cause a 'panic' when compiled in debug mode but are allowed to exist in release mode. However, some methods can support explicit overflows, wrapping, or saturation. It is also possible to return None on an overflow using the checked method.

Beyond the language, there are also a host of useful tools. Cargo is both the build and package manager. Rustfmt formats your source code with the correct indentation. Then there is Clippy, a lint tool that performs static code analysis, searching out strange code constructs and possible bugs.

Lastly, developers will undoubtedly want to integrate some existing C/C++ code. This can be done thanks to the Foreign Function Interface (FFI).
 
use libc::size_t;

#
extern {
    fn snappy_max_compressed_length(source_length: size_t) -> size_t;
}

fn main() {
    let x = unsafe { snappy_max_compressed_length(100) };
    println!("max compressed length of a 100 byte buffer: {}", x);
}

Example for calling C function "snappy" from Rust sourced from: https://doc.rust-lang.org/nomicon/ffi.html
 

How Can I Try Out Rust?

Like most projects, the tools for Rust are open-source and freely available. On top, there are plenty of websites providing documentation, tutorials, and guidance. Perhaps the easiest starting point is with your PC. Tutorials guide you through the process of installing the toolchain, followed by an introduction to the language.

There are several options if you're keen to understand Rust's capabilities in the area of embedded systems. Rust runs on Raspberry Pi and can control the available interfaces, allowing an LED to be toggled with relative ease. Alternatively, you can build code for the micro:bit and the STM32 family is well-supported. If you don't have hardware to hand, you could try spinning up an emulated microcontroller using QEMU.

If you're curious whether there are any real-time operating systems (RTOS), you may like to look at Bern or OxidOS (who Stuart Cording also interviewed at embedded world). There is also a way of providing Rust code with access to FreeRTOS. For a list of projects, check out https://arewertosyet.com/.

Subscribe
Tag alert: Subscribe to the tag embedded systems and you will receive an e-mail as soon as a new item about it is published on our website!

Will Rust Replace C for Embedded Systems?

If you're considering a career in embedded software, you may naturally be concerned that you're learning the wrong language. However, there is no need to panic. The industry has had decades to acquire benefits similar to those offered by Rust through Ada, but that language has only really penetrated the aerospace industry. Traditionally, the world of embedded has been slow to adopt practices commonplace in other branches of the software industry, so the chances of a seismic shift to Rust in the next decade remain low.

Despite this outlook, it is never a bad thing to broaden your horizon, especially if you have decades of career in front of you. Thanks to the Internet, freely available resources, and the general zeitgeist, your first Rust project may be closer than you think. 

Want to Publish an Article in Elektor Mag? Here's How