Skip to content
rusthacks.dev
Systems

No Std Rust Explained and What You Lose Without Std

no_std removes the standard library and leaves core. See what you lose, what core and alloc still give you, and how to write crates that work both ways.

2 min read
Summarize in AI
Flat illustration for the post “No Std Rust Explained and What You Lose Without Std”, composed around ownership.

The error says can't find crate for std, and the target is a microcontroller, so there is no std to find. Rust splits its standard library into layers, and no_std code uses only the lowest. This post draws the map, which is the thing that makes the next ten embedded errors readable. It supports the embedded Rust guide.

Rust 1.90, edition 2024.

The three layers

CrateNeedsGives
corenothingprimitives, Option, Result, iterators, slices, fmt, atomics, mem, ptr, traits
alloca global allocatorBox, Vec, String, Rc, Arc, BTreeMap, format!
stdan operating systemthreads, files, sockets, HashMap, Instant, environment, println!

#![no_std] links core automatically. It does not remove the prelude: Option, Some, Vec (if alloc is declared) and traits like Iterator are still there.

What changes in your code

rust
#![no_std]

use core::fmt::Write;

pub struct Buffer {
    data: [u8; 32],
    len: usize,
}

impl Write for Buffer {
    fn write_str(&mut self, s: &str) -> core::fmt::Result {
        let bytes = s.as_bytes();
        let end = self.len + bytes.len();
        if end > self.data.len() {
            return Err(core::fmt::Error);
        }
        self.data[self.len..end].copy_from_slice(bytes);
        self.len = end;
        Ok(())
    }
}

write!(buf, "temp {}", 21) works, formatting into a fixed array with no allocation. use std::fmt becomes use core::fmt. There is no println!; devices log through a serial port, semihosting or defmt.

Adding alloc

rust
extern crate alloc;
use alloc::{string::String, vec::Vec};

You also need a global allocator. A crate like embedded-alloc provides a heap you initialize with a memory region at startup. Allocation can fail, and the default behavior is to call an error handler, so many firmware projects avoid it entirely.

A crate that works both ways

rust
#![cfg_attr(not(feature = "std"), no_std)]

with a std feature in Cargo.toml and #[cfg(feature = "std")] on code that needs it. Features must be additive, so std can only add things, as covered in features and their unification. Test the no_std build with cargo build --no-default-features on a bare metal target in CI; a host build will not catch accidental std use, because the host has it.

Common errors

  • can't find crate for std: a dependency or a use std:: line; switch to core or fix the dependency's features.
  • #[panic_handler] function required: add panic-halt, panic-probe or your own.
  • language item required, but not found: eh_personality: build with panic = "abort".
  • no global memory allocator found: you used alloc types without a #[global_allocator].

Limits

Some crates claim no_std support but only for a subset of their features. Read their feature list before relying on them. The Embedded Rust Book chapter on no_std explains the layers from the language side.

Also inRust
Taggedno-std
Share

Keep reading

Get new posts by email

We send one email when a new post is published. No spam.

Unsubscribe in one click. No spam, ever.