core/*
core/* packages are always available and never require heap allocation. They form the baseline for bare-metal and MCU code.
core/clone: Explicit Duplication
core/clone defines the standard Clone trait used for explicit duplication of non-Copy values.
trait Clone {
fn clone(self) -> Self
}
Clone is also part of the global prelude, so you normally write impl Clone for Buffer without importing core/clone.
core/fmt: No-Heap Formatting
Format values without allocating:
use board/pico as board
use core/fmt
fn main() -> never {
let uart = board.default_uart().init()
let temp_c: f32 = board.temp_sensor().read_c()
fmt.write(uart, "temp={}C\r\n", temp_c)
loop {}
}
The compiler recognizes fmt.write(...) as a checked formatting call and validates the compile-time format string, including placeholder count.
Placeholder syntax:
{}: format one argument{{: literal{}}: literal}
Current executable path:
- sink: a concrete output handle whose type implements
core/io.Writer, such as a UART port - arguments: unit,
bool, integers,f32,char, string literals, and string locals
fmt.write(uart, "ok: {}", true)
fmt.write(uart, "n: {}", 42u32)
core/io: Reader and Writer Traits
use core/io
trait Writer {
fn write(mut self, bytes: slice<u8>) -> Result<usize, Error>
fn flush(mut self) -> Result<(), Error>
}
trait Reader {
fn read(mut self, buf: mut slice<u8>) -> Result<usize, Error>
}
These traits define the long-term sink/source abstraction surface:
fn dump_state(mut out: io.Writer) -> Result<(), Error> {
fmt.write(out, "state: running\r\n")?
return out.flush()
}
The current executable backend slice does not yet execute general runtime trait-dispatch through io.Writer and io.Reader parameters. Today, core/fmt.write(...) works by taking a concrete sink whose type implements core/io.Writer, then lowering the concrete sink path selected by that implementation.
core/sync: Atomics and One-Time Init
use core/sync
// Single-assignment global (safe for firmware globals)
static DEVICE_ID: sync.OnceCell<u32> = sync.OnceCell.new()
fn setup() -> () {
DEVICE_ID.set(read_chip_id())
}
fn get_id() -> u32 {
return DEVICE_ID.get().unwrap_or(0)
}
// Lazy one-time initialization
static CONFIG: sync.Lazy<Config> = sync.Lazy.new(|| -> Config {
return Config.load_from_flash()
})
fn use_config() -> () {
let c = CONFIG.get() // initializes on first access
apply(c)
}
Atomic operations for lock-free code:
use core/sync
static COUNTER: sync.AtomicU32 = sync.AtomicU32.new(0)
fn increment() -> () {
COUNTER.fetch_add(1, sync.Ordering.Relaxed)
}
fn get_count() -> u32 {
return COUNTER.load(sync.Ordering.Acquire)
}
core/cmp: Comparison
use core/cmp
let order = cmp.compare(a, b) // Ordering.Less, Ordering.Equal, Ordering.Greater
let mut items = [3, 1, 4, 1, 5, 9]
items.sort_by(|a, b| {
return cmp.compare(a, b)
})
core/embed: Compile-Time Assets
use core/embed
const FONT_DATA: slice<u8> = embed.bytes("assets/font.bin")
const INDEX_PAGE: string = embed.string("assets/index.html")
The path argument must be a string literal. The file is read at compile time and embedded in read-only image data. Invalid paths and invalid UTF-8 for embed.string are compile errors.
Dead code elimination removes unused embedded assets.
core/error
The Error type is from core/error and is always in scope without an import. You do not need to write use core/error.
fn validate(input: string) -> Result<(), Error> {
if input.is_empty() {
return Err("input cannot be empty")
}
return Ok()
}