do you want to take a deep dive into the differences between rust and c?

download our comparison

We’ve told you before about the advantages that Rust entails as a programming language. One of the most important advantages of this language is that errors in the software are discovered sooner - before the prototype gets developed. Rust is a safe language to use when developing embedded systems.

Rust also has a few other great features worth mentioning for those who want to use Rust in embedded. We’ll list them in this article, together with some interesting facts.

zero-cost abstractions.

One of the nice things about Rust is its zero-cost abstractions. This means that you can write code resembling a high-level language without the overhead cost. The compiler will typically generate the same instructions as if you were writing low-level code which is less human-readable. This also means that the compiler can check for subtle mistakes in the code, for example accessing a GPIO pin before initialization, setting an output to high when it is configured as an input ...

rust on bare metal.

In bare metal environments we do not have the luxury of an operating system providing a system interface such as POSIX and handling access to file systems, networking etc for us. This means Rust should not bother loading the standard library. Using the #![no_std] attribute we instead will link with the libcore crate.

This is a platform-agnostic subset of the std crate that makes no assumptions about the system the software will run on.

available features for rust on a bare metal target

feature no_std std
heap (dynamic memory) *
collections (Vec, HashMap, etc) **
stack overflow protection X
runs init code before main X
libstd available X
libcore available
writing firmware, kernel, or bootloader code X

* Only if you use the alloc crate and use a suitable allocator like alloc-cortex-m.
** Only if you use the collections crate and configure a global default allocator.

As you can see, when dynamic memory allocation is a necessity in your project, some more steps need to be taken to regain this functionality. Alternatively, the heapless crate can be used which requires no setup but does require the developer to define the capacity of a collection at compile time.

memory-mapped peripherals.

Everyone working with memory-mapped peripherals knows that defining a variable as volatile is required so the compiler does not optimize away this variable. In Rust, it is the access that is marked volatile rather than the variable. This means we use the read_volatile() and write_volatile() functions on pointers we are working with.

introduce rust in your c environment… and vice versa.

We know that software isn’t built in one day and many companies have millions of lines of code in critical legacy software written in C. Rewriting all of this is an impossible task, and we do not recommend it. 

However, new features or safety-critical software can be written in Rust and can easily integrate with your C software. This is a less daunting task and takes it one step at a time. Using the cbindgen tool, developers can quickly generate boilerplate code to call Rust functions from a C environment and vice versa using rust-bindgen.

Using cargo, we can create a shared object or archive file which the C linker can pick up and integrate into the final binary.

Thanks to bindgen it’s relatively easy to use a driver binary blob from a silicon vendor that has a C interface and interact with it from within Rust code.

A man looking at his computer.

existing libraries.

Due to its age, Rust’s ecosystem is not as mature as other languages. There are however already many useful crates that can be used in production without issues. Thanks to Cargo and the crates.io website, it is extremely easy to incorporate these into a project without having a dependency hell. 

Crates use semantic versioning which means you specify which version of a crate you’d like and Cargo can make sure it uses the latest version that guarantees not to break your code after an update while still getting bug fixes for example. However, if a very specific version is required Cargo can be instructed to only use that one. 

Lastly, if you have dependencies on private libraries, you can instruct Cargo to fetch it from a Git repository or a specific path on the filesystem.

embedded-hal.

The go-to crate when writing Rust on an embedded target. This crate nicely abstracts away all the underlying hardware access code by providing a clear and easy to use API. Several architectures and boards are already supported with more being added continuously.

cty.

Cty is used in combination with bindgen to more easily interface your Rust code with C code. It provides type aliases to C types such as c_int, c_uchar and c_void.

serde.

Everyone who has worked with de-serialization of data knows the pain it can cause to develop a robust system. The Serde crate works all the way down to embedded systems, and is a great way to avoid messy memory management or byte-packing for custom formats as well.

glossary

term explanation
crate Rust’s name for a module or library
std Rust Standard Library
bindgen Tool to create foreign function interfaces between Rust and C/C++
about the author

devon kerkhove

project manager

Working on the (invisible) technology of tomorrow, together with our clients, that's what gets us going. Bitten by embedded systems, driven by innovation.