Rust

Rust for Embedded Systems

Rust is modern programming language first designed by Graydon Hoare at Mozilla Research in 2010, it focuses on performance, safety and especially safe concurrency. It guarantees memory safety without garbage collection or a runtime.

Rust is becoming more popular in recent years and it is our believe that it will be an important language in the embedded sector in the near future. Rust is already starting to be used within the Linux kernel and can often be used as an alternative to C for new projects or developments.

In this post we will be cross-compiling a simple ‘hello world’ binary for aarch64 (although the instructions should be true for all target architectures supported by Rust).

Installation of Rust and cross compilers

The first step with any cross-compilation is getting a working set of tools, Rust makes this a relatively simple process – we simply download and execute the rustup-init shell script. This script will install and run rustup which is a toolchain ‘multiplexer’ that allows you to install and manage Rust toolchains.

$ curl https://sh.rustup.rs -sSf | sh

Now that the main Rust components are installed we need to focus on gaining a cross-compliation environment. For this we need a few more additional components: a Rust cross-compiler (rather than one for the host) and a GCC cross-compiler (used for linking).

For the Rust cross-compiler it’s simply a case of using the ‘rustup‘ command line utility to install the required toolchain:

$ rustup target list # Lists all available Rust compilers
$ rustup target add aarch64-unknown-linux-gnu
info: downloading component 'rust-std' for 'aarch64-unknown-linux-gnu'
info: installing component 'rust-std' for 'aarch64-unknown-linux-gnu

Rust binaries are compiled with the Rust compiler but are linked using GCC’s LD as the linker (read more here). In this post we install the latest binary release directly from the ARM website (10.0-2020.11), however it is recommended that you use the same compiler as used by the rest of your target system. You can install the ARM cross-compiler by running:

$ wget https://developer.arm.com/-/media/Files/downloads/gnu-a/10.2-2020.11/binrel/gcc-arm-10.2-2020.11-x86_64-aarch64-none-linux-gnu.tar.xz
$ tar -xf  gcc-arm-10.2-2020.11-x86_64-aarch64-none-linux-gnu.tar.xz -C ~/tools
$ export PATH=~/tools/gcc-arm-10.2-2020.11-x86_64-aarch64-none-linux-gnu/bin:$PATH

Cross compiling using the Rust compiler

In this section we will learn how to use the Rust compiler rustc, directly to cross-compile our simple ‘hello world’ application. First a simple Rust program:

$ cat hello_world.rs
fn main() {
    println!("Hello, world!");
}

To cross-compile this we simply have to pass in the options for rustc to use:

$ rustc --target aarch64-unknown-linux-gnu -C linker=aarch64-none-linux-gnu-gcc hello_world.rs

Where aarch64-unknown-linux-gnu is the Rust cross-compiler target we installed earlier, and aarch64-none-linux-gnu-gcc is GCC. This will give us a hello_world binary in the same folder which can be copied to the target

# ./hello_world
Hello, world!

However, rather than directly invoking rustc – Rust comes with it’s own package management tool called Cargo which is typically used instead.

Cross compiling using Cargo

Cargo is a build system and package manager for Rust. It can help with the downloading of packages, maintain dependencies and assists with the creating of new packages. Let’s try building a hello world application (known as a binary Crate in Rust speak) with Cargo.

First the Rust environment needs to be added to your current shell process, again this has been made very easy:

$ source ${HOME}/.cargo/env

To create a new ‘hello world’ Cargo package, just run:

$ cargo new hello_world
     Created binary (application) `hello_world` package

This will have created a new ‘hello_world’ folder with 2 files: Cargo.toml, the manifest file for the package; and src/main.rs, the implementation of our ‘hello world’ program.

Before cross-compiling with Cargo we need to configure Cargo to use our GCC linker:

$ cat ~/.cargo/config
[target.aarch64-unknown-linux-gnu]
linker = "aarch64-none-linux-gnu-gcc"

We are now ready to compile up the package:

$ cd hello_world
$ cargo build --target aarch64-unknown-linux-gnu
   Compiling hello_world v0.1.0 (/home/username/hello_world)
    Finished dev [unoptimized + debuginfo] target(s) in 0.13s

This will produce our cross-compiled binary: target/aarch64-unknown-linux-gnu/debug/hello_world ready for installing and running.

# ./hello_world
Hello, world!

Rust is a very attractive language: besides it’s memory and thread safety – it also has good interoperability with C and C++. It even integrates well with existing build systems such as buildroot and Yocto.

Popular Posts