ajhahn.de
← all chapters

Flash tutorial · 12 / 12

12. The Standard Library

Flash ships the seed of its own standard library — written in Flash. The library is one named module, core; a program imports it with use core and reaches every submodule through it (core.mem.eql, core.list.List, …).


1. One Door to Zig

A single confinement rule shapes the whole library: base is the only std module allowed to import Zig's std. Every other module is pure Flash and imports only base and its sibling modules, so the toolchain dependency stays behind one door. The surface deliberately mirrors Zig's std spellings, so code can move between the two implementations without rewriting call sites.

The library is not an abstraction layer over the language — it is ordinary Flash code, and its in-module test blocks double as usage examples.


2. The Modules

Module What it provides
core.mem Slice and memory primitives — eql, indexOf, copy, set, a stable sort, explicit-endian readInt / writeInt, and byte views (asBytes).
core.list List(T) — the dynamic array.
core.fmt allocPrint / bufPrint formatting with compile-time-checked format strings, and overflow-checked parseInt.
core.math Integer bounds: minInt / maxInt, exact at every bit width.
core.arena ArenaAllocator — everything allocated through it is released by one deinit.

3. List(T) — the Dynamic Array

List(T) is a generic function returning a struct — the library's workhorse container. .empty initializes without allocating; every growing operation takes the allocator explicitly:

use core

var s core.list.List(u8) = .empty
defer s.deinit(alloc)

try s.appendSlice(alloc, "flash")
try s.append(alloc, '!')

// .items is the live slice — index, slice, and iterate it directly
ok := core.mem.eql(u8, s.items, "flash!")

On allocation failure the list is left untouched and still usable — every error path in the library is proven by an induced-failure test sweep.


4. Formatting & Parsing

The format string is walked at compile time, so an unknown verb or a mismatched argument count is a compile error, not a runtime surprise:

use core

// Allocating: returns a freshly allocated string
msg := try core.fmt.allocPrint(alloc, "{d} items", .{count})

// Allocation-free: formats into a caller buffer, returns the written prefix
var buf [32]u8 = undefined
line := try core.fmt.bufPrint(&buf, "0x{x}", .{addr})

// Parsing mirrors Zig exactly: sign, base-prefix auto-detect under radix 0,
// '_' separators, overflow-checked accumulation
n := try core.fmt.parseInt(i32, "-128", 10)

5. The Arena Allocator

An arena trades fine-grained free for one-shot cleanup: allocations bump a cursor inside chunks of backing memory, and a single deinit releases everything at once. The handle it returns is a real allocator, so anything that takes one works through the arena unchanged:

use core

var arena = core.arena.ArenaAllocator.init(child_alloc)
defer arena.deinit()

alloc := arena.allocator()

// Allocate freely; no individual frees needed
var xs core.list.List(i32) = .empty
try xs.append(alloc, 7)

6. Running the Library's Tests

In the Flash repository, the standard library tests itself through its own test blocks:

zig build test-std    # transpile and run the std test suite

The std sources are also part of the compiler's bootstrap fixpoint corpus, so every release proves they transpile deterministically.