๐ŸŽ“ A1Learner
๐Ÿ”

Rust Without the Standard Library

no_std means building Rust without the standard library.

The one-line switch

One line at the very top of the file does it. The crate now links core instead of std:

#![no_std]

Three layers, not one

std is a stack of layers. Each layer asks for more from the machine:

// core  โ€” slices, iterators, Option, Result, maths
// alloc โ€” Vec, String, Box (needs an allocator)
// std   โ€” files, threads, networking, println!

What core still gives you

Plenty survives the cut. This helper uses only slices and iterators. It could live in any no_std crate:

pub fn max_reading(readings: &[u32]) -> Option<u32> {
    readings.iter().copied().max()
}

std is simply not there

Reach for std and the compiler refuses. The crate was never linked, so the name does not exist:

pub fn oops() -> std::vec::Vec<u32> { std::vec![1, 2, 3] }
// error[E0433]: cannot find module or crate `std` in this scope

The middle layer: alloc

Need a Vec after all? Add alloc back. A library may use it freely. Only the final program must bring an allocator:

extern crate alloc;
use alloc::vec::Vec;

pub fn doubled(readings: &[u32]) -> Vec<u32> {
    readings.iter().map(|r| r * 2).collect()
}

Example

// A complete program with no standard library.
// It builds on this Linux host with panic = "abort" in Cargo.toml
// and -nostartfiles passed to the linker. The next lesson explains both.
#![no_std]
#![no_main]

use core::panic::PanicInfo;

#[link(name = "c")]
unsafe extern "C" {
    fn write(fd: i32, buf: *const u8, count: usize) -> isize;
    fn exit(code: i32) -> !;
}

#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
    let msg = b"panic: giving up\n";
    unsafe {
        write(2, msg.as_ptr(), msg.len());
        exit(101)
    }
}

#[unsafe(no_mangle)]
pub extern "C" fn _start() -> ! {
    let msg = b"hello, no_std\n";
    unsafe {
        write(1, msg.as_ptr(), msg.len());
        exit(0)
    }
}

Key points

  • core: the bottom layer.
  • It holds slices, iterators, Option, Result and maths.
  • It assumes nothing about the system, so it runs anywhere
  • alloc: the middle layer.
  • It adds heap types like Vec, String and Box.
  • It needs an allocator, but no operating system
  • std: the top layer.
  • It adds files, threads, networking and println!.
  • It needs an operating system underneath
  • Use #![no_std] at the top of a crate to build it against core alone.
  • Reach for alloc only when you need the heap
  • A no_std library still tests on your own computer.
  • cargo test brings std back for the test harness only.
  • The library itself stays clean
  • println! lives in std.
  • A no_std program has no console waiting for it.
  • Output is your own job now.
  • A no_std crate links core instead of the standard library.
  • Some computers have no operating system at all, and Rust can run there by leaving std behind.

In this example, the first line removes std. The second line removes the usual main. Nobody sets the stage now, so the program starts at _start, a name the machine looks for. There is no println!, so the program writes raw bytes through the C library write function. The panic handler sits unused this time. Run it on this Linux computer and it prints hello, no_std, then exits cleanly. Real chips start differently, but the shape is the same. No std. No main. Your code first.

Note

The program at the end of this lesson writes its line by hand

Try it Yourself

Edit the code, then press Run.

Running is simulated ยท Rust Playground

Exercise

What does #![no_std] actually remove?