Low-Level Systems Programming
The core and alloc Libraries
Rust's standard library is architected in three concentric layers:
core: The dependency-free foundation providing language primitives, slices, iterators, formatting traits, futures, and atomics. Requires no OS and no heap allocator.alloc: The heap-allocation layer (extern crate alloc;) providing smart pointers (Box,Rc,Arc) and dynamically sized collections (Vec,String,VecDeque,BTreeMap,BTreeSet,BinaryHeap,LinkedList) on top of any#![no_std]target that defines a#[global_allocator].std: The hosted OS layer that re-exportscoreandallocwhile adding OS threads, filesystems, networking, environment variables, andHashMap(which sits instdbecause its defaultRandomStateseeds SipHash from OS entropy!).
Mapping Modules Across core, alloc, and std
| Module / Type | Available in core? |
Available in alloc? |
Available Only in std? |
Notes for #![no_std] Code |
|---|---|---|---|---|
option, result, iter, slice, mem, ptr, ops, cmp |
Yes (core::*) |
Re-exported | Re-exported | Always available everywhere. |
fmt::Display, fmt::Debug, fmt::Write |
Yes (core::fmt) |
Re-exported | Re-exported | Use core::write! into a stack buffer or UART without heap allocation! |
error::Error, ffi::CStr, net::IpAddr, net::SocketAddr |
Yes (core::error, core::ffi, core::net) |
Re-exported | Re-exported | core::net (Rust 1.77+) and core::error::Error (Rust 1.81+) work in core! |
Box, Rc, Arc, Cow, Vec, String, BTreeMap |
No | Yes (alloc::*) |
Re-exported | Requires extern crate alloc; and a registered #[global_allocator]. |
format! macro |
No | Yes (alloc::format!) |
Re-exported | Allocates a String on the heap. |
HashMap, HashSet |
No | No | Yes (std::collections) |
In no_std + alloc, use alloc::collections::BTreeMap or the hashbrown crate directly. |
fs, net::TcpStream, thread, process, env, io::Read/Write |
No | No | Yes (std::*) |
Require operating system kernel services. |
Formatting Without Heap Allocation Using core::fmt::Write
Even in a pure #![no_std] environment without alloc, you can use Rust's full write! formatting machinery ({:08x}, {:?}, {:.2}) by implementing core::fmt::Write on a fixed-size stack buffer or hardware serial port:
use core::fmt::{self, Write};
use core::str;
/// A fixed-capacity stack string buffer that implements `core::fmt::Write` with zero heap allocation!
pub struct StackString<const CAP: usize> {
buf: [u8; CAP],
len: usize,
}
impl<const CAP: usize> StackString<CAP> {
pub const fn new() -> Self {
Self {
buf: [0u8; CAP],
len: 0,
}
}
pub fn as_str(&self) -> &str {
// SAFETY: `write_str` only appends valid UTF-8 `&str` slices into `self.buf[..self.len]`.
unsafe { str::from_utf8_unchecked(&self.buf[..self.len]) }
}
}
impl<const CAP: usize> Write for StackString<CAP> {
fn write_str(&mut self, s: &str) -> fmt::Result {
let bytes = s.as_bytes();
let end = self.len.checked_add(bytes.len()).ok_or(fmt::Error)?;
if end > CAP {
return Err(fmt::Error); // Capacity exceeded
}
self.buf[self.len..end].copy_from_slice(bytes);
self.len = end;
Ok(())
}
}
fn main() {
let mut line = StackString::<64>::new();
write!(&mut line, "sensor={:#04x}, temp={}C", 0x2a, 37).unwrap();
assert_eq!(line.as_str(), "sensor=0x2a, temp=37C");
println!("Formatted via core::fmt::Write on stack: {}", line.as_str());
}
Formatted via core::fmt::Write on stack: sensor=0x2a, temp=37C
Enabling alloc and Custom Global Allocators (#[global_allocator])
To use alloc (Vec, Box, String, BTreeMap) inside a #![no_std] crate:
- Declare
extern crate alloc;at the top ofsrc/lib.rsorsrc/main.rs. - In the final binary crate, register a type implementing
core::alloc::GlobalAllocwith the#[global_allocator]attribute (such asembedded-alloc::LlffHeapon Cortex-M or a bump/arena allocator):
extern crate alloc;
use alloc::collections::BTreeMap;
use alloc::vec::Vec;
use std::alloc::{GlobalAlloc, Layout, System};
use std::sync::atomic::{AtomicUsize, Ordering};
/// A wrapper around the system allocator that tracks total bytes allocated.
pub struct CountingAllocator;
static ALLOCATED_BYTES: AtomicUsize = AtomicUsize::new(0);
unsafe impl GlobalAlloc for CountingAllocator {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
ALLOCATED_BYTES.fetch_add(layout.size(), Ordering::Relaxed);
unsafe { System.alloc(layout) }
}
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
ALLOCATED_BYTES.fetch_sub(layout.size(), Ordering::Relaxed);
unsafe { System.dealloc(ptr, layout) }
}
}
#[global_allocator]
static GLOBAL: CountingAllocator = CountingAllocator;
fn main() {
let before = ALLOCATED_BYTES.load(Ordering::Relaxed);
let mut map = BTreeMap::new();
map.insert("node_1", Vec::from([10u64, 20, 30]));
let during = ALLOCATED_BYTES.load(Ordering::Relaxed);
assert!(during > before);
println!("Custom #[global_allocator] tracked {} active heap bytes", during - before);
}
Custom #[global_allocator] tracked 312 active heap bytes