Embedded and IoT

What is no_std Rust?

no_std Rust is Rust code compiled without the standard library. A crate marked with the `no_std` attribute links to `core`, the platform-agnostic part of the library, instead of `std`, which assumes an operating system. It is how Rust is written for firmware, bootloaders and kernels on hardware with no operating system underneath.

Learning objectives

After reading this article you will be able to:

  • Explain what building without the standard library changes and when firmware needs it
  • Distinguish what the core, alloc and std libraries each provide
  • List what a no_std program must supply, from panic handler to memory layout

What the attribute does

Rust ships with layered libraries. At the bottom is core, which the Rust documentation calls the dependency-free foundation of the standard library: it links to no upstream libraries, no system libraries and no libc. On top sits alloc, with heap types such as Box and Vec. At the top is std, which adds threads, files, sockets, processes and the other services you expect from a general-purpose operating system.

Putting #![no_std] at the root of a crate tells the compiler not to link std automatically, and to use the core prelude instead of the standard one. The Rust Reference names two reasons to do it: the crate targets a platform that does not support the standard library, or it deliberately does without the standard library’s capabilities, mainly dynamic memory allocation and file and network access.

In bare-metal embedded work, the first reason applies. On a microcontroller with no operating system, there is nothing for std to call. The Embedonomicon, the Rust embedded working group’s guide to bare-metal Rust, puts it plainly: a #![no_std] application can be the first or the only code that runs on a system, which makes it suitable for firmware, bootloaders and kernels.

What you keep and what you lose

core still gives you the language: primitive types, floats, strings, slices, iterators, Option and Result, and access to processor features such as atomic operations and SIMD instructions. What it lacks is anything that needs platform integration: heap allocation, I/O and concurrency.

The Embedded Rust Book summarises the difference:

Featureno_stdstd
Heap (dynamic memory)Only with alloc and an allocatorYes
Collections such as Vec and BTreeMapOnly with a global allocatorYes
Stack overflow protectionNoYes
Runs initialisation code before mainNoYes
Writing firmware, kernel or bootloader codeYesNo

The book adds that HashMap and HashSet are not available in a no_std environment because there is no secure random number generator to seed them. That is a reminder of something easy to forget: on bare metal, randomness is your job too.

What a no_std program has to provide

Leaving out std also leaves out its runtime. std sets up stack overflow protection, processes command-line arguments and spawns the main thread before main runs. A no_std application must set up whatever it needs itself. In practice that means a few pieces:

  • A panic handler. core expects the program to define what happens on a panic. The Rust Reference requires exactly one function marked #[panic_handler] in the whole dependency graph, with a signature that never returns. The Reference’s own example logs the panic message and then halts.
  • An entry point. The Embedonomicon’s smallest program uses #![no_main], because Rust’s usual main assumes things such as command-line arguments. A runtime crate for the target, such as cortex-m-rt for Arm Cortex-M parts, supplies the real entry point and startup code.
  • A memory layout. The linker has to know where flash and RAM are. The Embedded Rust Book’s Cortex-M template keeps this in a memory.x file that lists the part’s flash and RAM.
  • An allocator, if you want one. To use alloc, declare a global allocator. The Embedded Rust Book also points to fixed-capacity collections in the heapless crate as an alternative.

You also build for a target triple that has no operating system in its name, such as thumbv7em-none-eabihf. Rust’s platform support list marks the targets that only support no_std development.

Libraries that work both ways

A crate can be useful on both a desktop and a device. The Cargo Book’s advice is to make no_std the base and add a std feature that turns the standard library back on, rather than a no_std feature that takes it away, because Cargo features should be additive. A user on bare metal then installs the crate with default features off.

Offline Protocol’s constrained-device crate shows the pattern from the user’s side. Its documentation, for Rust 1.87 or newer, installs it like this:

cargo add offline-protocol-leaf@=0.27.0 --no-default-features --features bare-metal-rng

The docs say not to combine bare-metal-rng with the default std feature, and to register the device’s hardware entropy source with the register_custom_getrandom macro from the getrandom crate, because enabling the feature alone does not supply randomness. A missing or predictable source makes key generation unusable or unsafe, the same issue that keeps HashMap out of a no_std build. For what else encrypted messaging needs on small hardware, see running encrypted mesh messaging on a microcontroller.

Where no_std ends

no_std is a property of a crate, not of a chip. Some embedded frameworks provide enough of an operating system for std, and the same platform list includes embedded targets with the full standard library alongside bare-metal targets for the same processors. The ESP32 family is one case where both kinds of target exist. Choose no_std when there is no operating system, when you need full control of memory and startup, or when you are writing a library that should run anywhere.

Frequently asked questions

Can I use Vec or String in no_std Rust?

Yes, through the `alloc` crate, once the program declares a global allocator with the `#[global_allocator]` attribute. Without an allocator, fixed-capacity collections, such as those in the heapless crate the Embedded Rust Book mentions, keep everything in static memory or on the stack.

Does no_std guarantee that std is never linked?

No. The Rust Reference warns that a crate or any of its dependencies can still write `extern crate std`, which links the standard library into the program. Check your dependencies' features as well as your own attribute.

Sources

Build it with Offline Protocol

The Offline Protocol embedded docs give the install command for `offline-protocol-leaf`, its constrained-device crate, with the default `std` feature turned off, and explain how to connect a hardware entropy source.

Read the Rust and constrained devices docs