Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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()
}