ajhahn.de
← Flash
Markdown 219 lines
<div align="center">

<picture>
  <source media="(prefers-color-scheme: dark)" srcset="assets/flash_logo_dark.png">
  <img src="assets/flash_logo_light.png" alt="Flash" width="240">
</picture>

<h1>Cookbook</h1>

<p><i>Small, copyable recipes for everyday Flash.</i></p>

<p>
  <a href="README.md"><b>README</b></a> ·
  <a href="VISION.md"><b>Vision</b></a> ·
  <a href="https://ajhahnde.github.io/Flash/"><b>Tutorial</b></a> ·
  <a href="REFERENCE.md"><b>Reference</b></a> ·
  <b>Cookbook</b> ·
  <a href="SETUP.md"><b>Setup</b></a> ·
  <a href="VERSIONING.md"><b>Versioning</b></a> ·
  <a href="CHANGELOG.md"><b>Changelog</b></a> ·
  <a href="LICENSE.md"><b>License</b></a>
</p>

</div>

---

Where the [Reference](REFERENCE.md) documents what every form means, this
page shows the handful of patterns that make Flash code pleasant to write
and to reread. Every example on this page is real, compiled code: the
recipes live verbatim in
[`examples/register/cookbook.flash`](examples/register/cookbook.flash),
where the compiler's own test gates build and run them, so a snippet here
cannot quietly rot.

The examples share one preamble. `use core` imports the
[standard library](std/README.md); `use std` reaches Zig's standard
library directly — that is where the `Allocator` type itself lives, and
aliasing it once keeps signatures short:

```flash
use std
use core

const Allocator = std.mem.Allocator
```

## Contents

1. [One arena per job](#1-one-arena-per-job)
2. [Error handling](#2-error-handling)
3. [Cast chains](#3-cast-chains)
4. [Bit manipulation](#4-bit-manipulation)
5. [Struct literals and the formatter](#5-struct-literals-and-the-formatter)
6. [JSON](#6-json)

---

## 1. One arena per job

Flash has no garbage collector and no hidden allocations — every function
that allocates takes an `Allocator`. The pattern that keeps this from
becoming bookkeeping: give each bounded job its own arena. Allocate freely
inside the job without tracking individual lifetimes, and one `deinit`
releases everything at once. Whatever must outlive the arena is copied out
to the caller's allocator before the function returns.

```flash
pub fn greeting(child Allocator, name []u8, attempt u32) ![]u8 {
    var arena = core.arena.ArenaAllocator.init(child)
    defer arena.deinit()
    alloc := arena.allocator()

    line := try core.fmt.allocPrint(alloc, "hello {s}, attempt {d}", .{ name, attempt })
    return child.dupe(u8, line)
}
```

This is also the contract the standard library leans on: `core.json.parse`,
for instance, never frees a partial tree on failure — hand it an arena and
the question disappears.

## 2. Error handling

Errors are values from named sets. Originate one with `error.Name`,
propagate with `try`, and recover at the call site with `catch` — the three
spellings cover almost everything:

```flash
pub const ConfigError = error{ NotFound, OutOfMemory }

pub fn valueOf(table [][]u8, key []u8) ConfigError![]u8 {
    for line in table {
        if core.mem.indexOfScalar(u8, line, '=') |eq| {
            if core.mem.eql(u8, line[0..eq], key) {
                return line[eq + 1 ..]
            }
        }
    }
    return error.NotFound
}

pub fn portOrDefault(s []u8) u16 {
    return core.fmt.parseInt(u16, s, 10) catch 8080
}
```

When a function builds something in steps, `errdefer` undoes the finished
steps if a later one fails — and stays silent on success:

```flash
pub fn dupePair(alloc Allocator, a []u8, b []u8) ![2][]u8 {
    first := try alloc.dupe(u8, a)
    errdefer alloc.free(first)
    second := try alloc.dupe(u8, b)
    return .{ first, second }
}
```

## 3. Cast chains

Pointer reinterpretation usually needs two casts: `#alignCast` to assert
the alignment, `#ptrCast` to change the type. Spell `#ptrCast` outermost —
the lowered Zig is then already in the order `zig fmt` canonicalizes to,
so generated code passes format checks untouched:

```flash
pub const Header = struct {
    magic u32,
    len u32,
}

pub fn asHeader(raw [*]mut u8) *mut Header {
    return #ptrCast(#alignCast(raw))
}
```

Both casts infer their result from where the value lands (here: the return
type), so the chain stays free of repeated type names.

## 4. Bit manipulation

The operators are Zig's, with Zig's precedence: bitwise `&`/`^`/`|` bind
tighter than comparisons, so masks compose without parentheses. `#truncate`
narrows by dropping high bits, and the wrapping operators (`+%`, `-%`,
`*%`) say "overflow is intended" at the call site:

```flash
pub const READY u32 = 1 << 5

pub fn isReady(reg u32) bool {
    return reg & READY != 0
}

pub fn green(rgba u32) u8 {
    return #truncate((rgba >> 8) & 0xFF)
}

pub fn nextSeq(seq u8) u8 {
    return seq +% 1
}
```

Plain `+` on a `u8` panics at 255 in safe builds; `+%` rolls over to 0 by
design. Pick per call site, not per project.

## 5. Struct literals and the formatter

Give fields defaults and most call sites shrink to the fields they care
about — `.{}` is a complete value when every field has one. Two formatting
habits keep the diffs quiet: write initializers on one line (the formatter
keeps them collapsed, so hand-wrapped literals are rewritten on the next
format), and zero fixed buffers with `**` repetition:

```flash
pub const Config = struct {
    name []u8 = "",
    retries u8 = 3,
    verbose bool = false,
}

pub fn defaultConfig() Config {
    return .{}
}

pub fn verboseConfig(name []u8) Config {
    return .{ .name = name, .retries = 5, .verbose = true }
}

pub var scratch [64]u8 = [_]u8{0} ** 64
```

`flashc fmt` emits one canonical form, so the cheapest habit of all is to
write that form from the start — wire your editor to format on save
([SETUP.md](SETUP.md) has the recipes) and the question never comes up.

## 6. JSON

`core.json` parses an RFC 8259 document into a tree of `Value` nodes.
Everything is allocated from the allocator you pass — combined with recipe
1, an arena makes the whole document one allocation lifetime. `get` looks
up an object member, and a `switch` on the value's tag unpacks it:

```flash
pub fn portOf(alloc Allocator, src []u8) !i64 {
    doc := try core.json.parse(alloc, src)
    value := doc.get("port") orelse return error.MissingPort
    return switch value {
        .int => |n| n,
        else => error.MissingPort,
    }
}
```

Numbers that scan as integers and fit an `i64` arrive as `.int`; fractions,
exponents, and oversized integers keep their raw lexeme in `.number`, which
`stringify` writes back verbatim — a parse/stringify round trip never loses
digits.