Frequently Asked Questions

§1 Does Rust support my device?

§1.1 Short answer

As of 2018-09-18 the Rust compiler supports cross compiling to these embedded architectures (see rustup target list):

rustc also supports code generation for the MSP430 architecture (see rustc --print target-list).

In general, ARM Cortex-M and ARM Linux have the most mature ecosystems whereas the ARM Cortex-R, MSP430 and RISCV ecosystems are in early stages or not as mature.

For specific device support check awesome-embedded-rust.

§1.2 Long answer

We can talk about support at different levels: does the compiler support my device? does the crate ecosystem support my device?

Let’s start with compiler support. The compiler supports architectures or ISA (Instruction Set Architectures) rather than specific devices. Compiler support can be further divided in two levels: compilation target support and architecture support.

§1.2.1 Compilation target support

By compilation target support we mean that you can readily cross compile a crate for a certain compilation target using Cargo. To keep the default installation slim only the native compilation target is installed and other targets have to be installed using the rustup target subcommand. If rustup target list lists a compilation target that matches your device then Cargo supports cross compiling to your device.

For example, let’s say we want to know if rustc supports cross compiling to 32-bit RISCV. We check rustup target list

$ rustup default 1.29.0
$ rustup target list | grep -i riscv || echo not supported
not supported

$ rustup default nightly-2018-09-18 # this date is just an example
$ rustc -V
rustc 1.30.0-nightly (2224a42c3 2018-09-17)
$ rustup target list | grep -i riscv || echo not supported
riscv32imac-unknown-none-elf
riscv32imc-unknown-none-elf

This indicates that 1.29 doesn’t support 32-bit RISCV but that 1.30 will.

Once you have installed a compilation target using rustup target add $T you’ll be able to cross compile crates to it using cargo build --target $T.

$ rustup target add riscv32imac-unknown-none-elf
$ cargo build --target riscv32imac-unknown-none-elf

§1.2.2 Architecture support

If your device doesn’t appear in rustup target list that doesn’t mean that rustc doesn’t support your device at all. It could still support code generation for your device architecture. rustc uses LLVM to generate machine code; if the LLVM backend for your device architecture is enabled in rustc then rustc can produce assembly and/or object files for that architecture.

The easiest way to list the architectures that LLVM supports is to run cargo objdump -- -version where cargo-objdump is one of cargo-binutils subcommands.

$ rustup default nightly-2018-09-18 # this date is just an example
$ rustup component add llvm-tools
$ cargo install cargo-binutils

$ cargo objdump -- -version
LLVM (http://llvm.org/):
  LLVM version 8.0.0svn
  Optimized build.
  Default target: x86_64-unknown-linux-gnu
  Host CPU: skylake

  Registered Targets:
    aarch64    - AArch64 (little endian)
    aarch64_be - AArch64 (big endian)
    arm        - ARM
    arm64      - ARM64 (little endian)
    armeb      - ARM (big endian)
    hexagon    - Hexagon
    mips       - Mips
    mips64     - Mips64 [experimental]
    mips64el   - Mips64el [experimental]
    mipsel     - Mipsel
    msp430     - MSP430 [experimental]
    nvptx      - NVIDIA PTX 32-bit
    nvptx64    - NVIDIA PTX 64-bit
    ppc32      - PowerPC 32
    ppc64      - PowerPC 64
    ppc64le    - PowerPC 64 LE
    riscv32    - 32-bit RISC-V
    riscv64    - 64-bit RISC-V
    sparc      - Sparc
    sparcel    - Sparc LE
    sparcv9    - Sparc V9
    systemz    - SystemZ
    thumb      - Thumb
    thumbeb    - Thumb (big endian)
    wasm32     - WebAssembly 32-bit
    wasm64     - WebAssembly 64-bit
    x86        - 32-bit X86: Pentium-Pro and above
    x86-64     - 64-bit X86: EM64T and AMD64

If your device architecture is not there that means rustc doesn’t support your device. It could be that LLVM doesn’t support the architecture (e.g. Xtensa, ESP8266’s architecture) or that LLVM’s support for the architecture is not considered stable enough and has not been enabled in rustc (e.g. AVR, the architecture most commonly found in Arduino microcontrollers).

If your device architecture is there then that means that, in principle, rustc supports your device. However, an architecture like ARM can be very broad covering several ISAs and extensions. Instead, you’ll want to work with a compilation target tailored to your device. Custom compilation targets are out of scope for this document; you should refer to the embedonomicon for more information.

§1.2.3 Crate ecosystem support

Crate ecosystem support can range from generic support for the architecture to device-specific support. We recommend that you search on crates.io for the architecture (e.g. ARM or Cortex-M), for the microcontroller vendor (e.g. STM32), for the target device (e.g. STM32F103) and the target development board (e.g. STM32F3DISCOVERY). We also suggest that you check the awesome-embedded-rust list and the crates maintained by the embedded Working Group.

§2 (When) will Rust support the AVR architecture?

As of 2020-07-24 the support for AVR have been merged into the nightly version of the official Rust compiler, rustc. See the RFC for merging the avr-rust fork.

§3 (When) will Rust support the Xtensa architecture?

As of 2020-08-24 the official Rust compiler, rustc, relies on LLVM for generating machine code. It’s a requirement that LLVM supports an architecture for rustc to support it.

There is no support for the Xtensa architecture in LLVM proper. You may be able to find several forks of LLVM with varying levels of support for the Xtensa architecture but rustc will not be able to use any of those forks due to the maintenance and infrastructure costs of developing rustc against different versions of LLVM.

TL;DR rustc will support the Xtensa architecture when the official LLVM gains support for the Xtensa architecture. As LLVM is a project independent of the Rust project we can’t give you any estimate on when that might happen. A list of submitted patches can be found on this LLVM dashboard.

§4 My embedded Rust program is too big!

We sometimes get questions like this one: “My Rust program is 500 KB but my microcontroller only has 16 KB of Flash; how can I make it fit?”.

The first thing to confirm is that correctly measuring the size of your program. rustc produces ELF files for most embedded targets. ELF files have metadata and contain debug information so measuring their size on disk with e.g. ls -l will give you the wrong number.

$ # 500 KB?
$ ls -hl target/thumbv7m-none-eabi/debug/app
-rwxr-xr-x 2 japaric japaric 554K Sep 19 13:37 target/thumbv7m-none-eabi/debug/app

The correct way to measure the size of an embedded program is to use the size program or the cargo size subcommand.

$ # ~ 2 KB of Flash
$ cargo size --bin app -- -A
    Finished dev [unoptimized + debuginfo] target(s) in 0.01s
app  :
section               size        addr
.vector_table         1024   0x8000000
.text                  776   0x8000400
.rodata                208   0x8000708
.data                    0  0x20000000
.bss                     0  0x20000000
.debug_str          145354         0x0
.debug_abbrev        11264         0x0
.debug_info         139259         0x0
.debug_macinfo          33         0x0
.debug_pubnames      40901         0x0
.debug_pubtypes      14326         0x0
.ARM.attributes         50         0x0
.debug_frame         21224         0x0
.debug_line         117666         0x0
.debug_ranges        63800         0x0
.comment                75         0x0
Total               555960

Of the standard sections, .text, .rodata and .data will occupy Flash / ROM; .bss and .data will occupy RAM; .debug_*, .ARM.attributes and .comments can be ignored as they won’t be loaded into the target device memory. For the other sections you’ll have to check your dependencies’ docs.

In this examples the program will occupy 2008 bytes of Flash.

Note that most (all?) runtime crates, like cortex-m-rt, will check at link time that the program fits in the target device memory. If it doesn’t fit you’ll get a linker error and no output binary. So, provided that you correctly entered the size of the memory regions of your device then if it links it should fit in the target device!

If you are measuring size using the right method and your program is still too big then check out our section on optimizations.

TODO(resources team) add link to the optimizations section

§5 I’m trying to follow the book but my program won’t flash

It is quite common that by oversight the linker configuration is not suitable for the target device. Indications for such a problem are loading errors in gdb:

(gdb) load
Loading section .vector_table, size 0x400 lma 0x0
Loading section .text, size 0x2064 lma 0x400
Load failed

Please note the lma which is the loading address and needs to match up with the start address of the flash.

Similarly openocd will indicate such a problem:

...
auto erase enabled
Info : device id = 0x10036422
Info : flash size = 256kbytes
Warn : no flash bank found for address 0
wrote 0 bytes from file target/thumbv7em-none-eabihf/debug/examples/hello in 0.001434s (0.000 KiB/s)

If you see such a message you will need to check your linker configuration, usually in memory.x for the correct addresses (and ideally also sizes). Please also note that after a change you will need to cargo clean and rebuild to use the changed addresses.

§6 My program just halts without connected debugger. What am I doing wrong?

§6.1 Short answer

If you’re using semihosting, switch to a different input / output mechanism.

§6.2 Long answer

An embedded MCU will typically stop working if exceptions (which is the MCUs way of signalling special or abnormal events) occur which are not handled and cleared, either by an exception handler or a connected debugger. The latter case is especially interesting because there’s a method of interacting with a connected computer dubbed semihosting which will work iff a debugger is properly connected and debugging software running and correctly set up. This method uses special processor instructions (e.g. a service or breakpoint call) to alert the connected system about input and/or output requests from the device. If no debugger is available to handle such a situation, the system either wait indefinitely or generate an even stronger exception which can only be cleared by a reset.

There’re two common scenarios which cause such a lockup unintentionally:

Please note that examples may make use of the semihosting concept but this should not be used in production setups due to the lack of connected debugger and a host running appropriate debugging software. The use of a serial interface is often a better choice. However if you just would like to see your example running please make sure your setup is ready to deal with the semihosting requirements.