rust-no-std
Rust no_std
Purpose
Guide agents through #![no_std] Rust development: what core and alloc provide vs std, implementing custom global allocators, panic handler selection for embedded targets, and strategies for testing no_std crates on the host machine.
When to Use
Use this skill when writing or debugging #![no_std] Rust code — library crates for embedded targets, or bare-metal firmware that cannot link against std. For the full embedded development workflow (probe-rs flashing, defmt logging, RTIC), use skills/embedded/embedded-rust. For cross-compilation target setup, use skills/rust/rust-cross. This skill focuses specifically on the no_std / core / alloc boundary and panic handler selection.
Examples
- "I need a parser crate that works without std" → structure with
#![no_std], feature-gate alloc APIs, use borrowed slices for core API - "How do I use Vec in a no_std environment?" → add
allocfeature, provide a global allocator (e.g.,linked-list-allocator), usealloc::vec::Vec - "How do I test my no_std crate on my laptop?" → use
#![cfg_attr(not(test), no_std)]to allow std in test mode, orcargo test --target x86_64-unknown-linux-gnu
Workflow
1. no_std crate structure
// src/lib.rs
#![no_std]
// core is always available (no OS needed)
use core::fmt;
use core::mem;
use core::slice;
// alloc: heap collections — requires a global allocator
#[cfg(feature = "alloc")]
extern crate alloc;
#[cfg(feature = "alloc")]
use alloc::{vec::Vec, string::String, boxed::Box, format};
pub fn add(a: u32, b: u32) -> u32 {
a + b
}
# Cargo.toml
[features]
default = []
alloc = [] # opt-in to heap allocation
[dependencies]
# no_std-compatible dependencies only
2. core vs alloc vs std
| Crate | Requires OS | Requires heap | Provides |
|---|---|---|---|
core |
No | No | Primitives, traits, iter, fmt, mem, ptr, slice, option, result |
alloc |
No | Yes (allocator) | Vec, String, Box, Arc, Rc, HashMap (requires global allocator) |
std |
Yes | Yes | All of core + alloc + OS APIs (threads, files, sockets, env) |
std re-exports everything in core and alloc, so use std::fmt and use core::fmt are equivalent when std is available.
What's available in core only (no heap, no OS):
// These work in no_std:
core::fmt::Write // trait for write! macro
core::iter // iterators
core::ops // operators (+, -, *, Deref, etc.)
core::option::Option
core::result::Result
core::mem::{size_of, align_of, swap, replace}
core::ptr::{read, write, null, NonNull}
core::slice, core::str
core::sync::atomic // atomic types
core::cell::{Cell, UnsafeCell, RefCell}
core::cmp, core::convert, core::clone, core::default
core::num // numeric conversions
core::panic::PanicInfo // for panic handler
3. Custom global allocator
To use alloc crate in no_std, provide a global allocator:
// src/allocator.rs — embedded allocator using linked_list_allocator
use linked_list_allocator::LockedHeap;
#[global_allocator]
static ALLOCATOR: LockedHeap = LockedHeap::empty();
pub fn init_heap(heap_start: usize, heap_size: usize) {
unsafe {
ALLOCATOR.lock().init(heap_start as *mut u8, heap_size);
}
}
[dependencies]
linked-list-allocator = { version = "0.10", default-features = false }
// src/main.rs (bare-metal)
#![no_std]
#![no_main]
extern crate alloc;
use alloc::vec::Vec;
mod allocator;
// In init code (after BSS/data init):
allocator::init_heap(0x20010000, 0x10000); // 64KB heap at RAM+64KB
// Now alloc types work:
let mut v: Vec<u32> = Vec::new();
v.push(42);
Common embedded allocator crates:
linked-list-allocator: general purpose,no_stdbuddy-alloc: power-of-two buddy systemdlmalloc: port of Doug Lea's malloctalc: fast, suited for embedded
4. Panic handler
In no_std, you must provide a panic handler — Rust requires one:
// Option 1: halt on panic (simplest, production)
use core::panic::PanicInfo;
#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
loop {} // spin forever
}
// Option 2: print panic info via defmt (embedded with debug probe)
#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
defmt::error!("{}", defmt::Display2Format(info));
cortex_m::asm::udf(); // undefined instruction → hard fault
}
// Option 3: use a panic crate (in Cargo.toml)
// panic-halt = "0.2" — spin loop
// panic-reset = "0.1.1" — reset MCU
// panic-probe = "0.3" — defmt + probe-rs
5. Writing portable no_std libraries
Design your library to work with and without alloc:
#![no_std]
#[cfg(feature = "alloc")]
extern crate alloc;
pub struct Parser<'a> {
data: &'a [u8], // borrowed slice: no allocation needed
pos: usize,
}
impl<'a> Parser<'a> {
pub fn new(data: &'a [u8]) -> Self {
Parser { data, pos: 0 }
}
// Core API: return borrowed data, no allocation
pub fn next_token(&mut self) -> Option<&'a [u8]> { /* ... */ None }
// Alloc API: only when alloc feature is enabled
#[cfg(feature = "alloc")]
pub fn collect_all(&mut self) -> alloc::vec::Vec<&'a [u8]> {
let mut tokens = alloc::vec::Vec::new();
while let Some(tok) = self.next_token() {
tokens.push(tok);
}
tokens
}
}
6. Testing no_std on host
# Cargo.toml
[dev-dependencies]
std = [] # allow std in tests only (via cfg)
[features]
std = []
// lib.rs
#![cfg_attr(not(test), no_std)] // no_std except during tests
// Tests compile normally with std — only library code is no_std
Or use a separate test harness:
# Run tests targeting the host (std available for test framework)
cargo test --target x86_64-unknown-linux-gnu
# Test with the actual embedded target using QEMU
cargo test --target thumbv7em-none-eabihf # fails: no test runner on bare metal
# Solution: use defmt-test or probe-run for on-target testing
# Or: architecture-neutral pure logic tests on host
# Check no_std compliance without hardware
cargo check --target thumbv7em-none-eabihf
cargo build --target thumbv7em-none-eabihf
Related skills
- Use
skills/embedded/embedded-rustfor probe-rs, defmt, and RTIC with no_std - Use
skills/rust/rust-crossfor cross-compilation target setup - Use
skills/rust/rust-unsafefor unsafe patterns needed in allocator implementations - Use
skills/embedded/linker-scriptsfor heap region placement in bare-metal targets
More from mohitmishra786/low-level-dev-skills
cmake
CMake build system skill for C/C++ projects. Use when writing or refactoring CMakeLists.txt, configuring out-of-source builds, selecting generators (Ninja, Make, VS), managing targets and dependencies with target_link_libraries, integrating external packages via find_package or FetchContent, enabling sanitizers, setting up toolchain files for cross-compilation, or exporting CMake packages. Activates on queries about CMakeLists.txt, cmake configure errors, target properties, install rules, CPack, or CMake presets.
580static-analysis
Static analysis skill for C/C++ codebases. Use when hardening code quality, triaging noisy builds, running clang-tidy, cppcheck, or scan-build, interpreting check categories, suppressing false positives, or integrating static analysis into CI. Activates on queries about clang-tidy checks, cppcheck, scan-build, compile_commands.json, code hardening, or static analysis warnings.
407llvm
LLVM IR and pass pipeline skill. Use when working directly with LLVM Intermediate Representation (IR), running opt passes, generating IR with llc, inspecting or writing LLVM IR for custom passes, or understanding how the LLVM backend lowers IR to assembly. Activates on queries about LLVM IR, opt, llc, llvm-dis, LLVM passes, IR transformations, or building LLVM-based tools.
361gdb
GDB debugger skill for C/C++ programs. Use when starting a GDB session, setting breakpoints, stepping through code, inspecting variables, debugging crashes, using reverse debugging (record/replay), remote debugging with gdbserver, or loading core dumps. Activates on queries about GDB commands, segfaults, hangs, watchpoints, conditional breakpoints, pretty-printers, Python GDB scripting, or multi-threaded debugging.
153linux-perf
Linux perf profiler skill for CPU performance analysis. Use when collecting sampling profiles with perf record, generating perf report, measuring hardware counters (cache misses, branch mispredicts, IPC), identifying hot functions, or feeding perf data into flamegraph tools. Activates on queries about perf, Linux performance counters, PMU events, off-CPU profiling, perf stat, perf annotate, or sampling-based profiling on Linux.
142core-dumps
Core dump analysis skill for production crash triage. Use when loading core files in GDB or LLDB, enabling core dump generation on Linux/macOS, mapping symbols with debuginfo or debuginfod, or extracting backtraces from crashes without re-running the program. Activates on queries about core files, ulimit, coredumpctl, debuginfod, crash triage, or analyzing segfaults from production binaries.
131