ajhahn.de
← Flash
Markdown 97 lines
# Chapter 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:

```flash
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:

```flash
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:

```flash
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:

```sh
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.