{
  "meta": {
    "zigVersion": "0.16.0",
    "zigReleased": "2026",
    "ziglingsVersion": "0.17.0-dev",
    "inspiredBy": "https://cheats.rs/"
  },
  "stats": {
    "sectionCount": 21,
    "entryCount": 120,
    "codeBlockCount": 127,
    "tableCount": 52,
    "ziglingsLinks": 66
  },
  "sections": [
    {
      "id": "getting-started",
      "title": "Getting Started",
      "icon": "Rocket",
      "blurb": "Install Zig, write your first program, master the CLI.",
      "entries": [
        {
          "id": "getting-started-install",
          "title": "Install & Verify",
          "summary": "One static binary, no package manager, nothing else to configure.",
          "keywords": [
            "install",
            "zig version",
            "download",
            "binary",
            "brew",
            "apt",
            "toolchain",
            "setup"
          ],
          "blocks": [
            {
              "kind": "text",
              "content": "Grab a build for your platform from **ziglang.org/download**. Zig ships as a single ~45 MB static binary — no runtime, no VM, no dependencies. Unpack it, put it on your `PATH`, done."
            },
            {
              "kind": "code",
              "lang": "sh",
              "title": "Verify the install",
              "code": "zig version\n# 0.16.0"
            },
            {
              "kind": "list",
              "items": [
                "Also available via `brew install zig` / `apt install zig` — but package-manager versions often lag behind the latest release.",
                "Upgrading is just replacing the folder; projects live in your own directories, not in the toolchain."
              ]
            }
          ]
        },
        {
          "id": "getting-started-hello",
          "title": "Hello, World! (0.16)",
          "since": "0.16",
          "ziglings": "001–002",
          "summary": "The canonical 0.16 main signature comes with an allocator, I/O handle and CLI args.",
          "keywords": [
            "hello world",
            "main",
            "std.process.init",
            "stdout",
            "std.debug.print",
            "zig run",
            "entry point"
          ],
          "blocks": [
            {
              "kind": "code",
              "lang": "zig",
              "title": "hello.zig",
              "code": "const std = @import(\"std\");\n\npub fn main(init: std.process.Init) !void {\n    try std.Io.File.stdout().writeStreamingAll(init.io, \"Hello, world!\\n\");\n}",
              "note": "The 0.16 Juicy Main signature: `init` bundles `init.gpa`, `init.io`, `init.arena` plus CLI args and environment."
            },
            {
              "kind": "list",
              "items": [
                "`std.debug.print(\"Hello!\\n\", .{})` writes to **stderr** and works with **any** main signature — ideal for quick prints.",
                "An empty `pub fn main() !void` is still legal — but then you get no access to CLI args or environment.",
                "`std.process.Init.Minimal` provides only args + environ, skipping the rest of the I/O setup."
              ]
            },
            {
              "kind": "code",
              "lang": "sh",
              "title": "Compile and run in one step",
              "code": "zig run hello.zig"
            }
          ]
        },
        {
          "id": "getting-started-cli",
          "title": "Zig CLI",
          "summary": "One tool compiles, tests, formats, cross-compiles — and even builds C code.",
          "keywords": [
            "zig run",
            "build-exe",
            "zig test",
            "zig fmt",
            "zig build",
            "zig cc",
            "translate-c",
            "zig fetch",
            "cli",
            "flags"
          ],
          "blocks": [
            {
              "kind": "table",
              "headers": [
                "Command",
                "What it does"
              ],
              "rows": [
                [
                  "`zig run file.zig`",
                  "Compile **and** run immediately"
                ],
                [
                  "`zig build-exe` / `-lib` / `-obj`",
                  "One-shot compile to executable, library or object file"
                ],
                [
                  "`zig test file.zig`",
                  "Build and run every `test` block"
                ],
                [
                  "`zig fmt .`",
                  "Canonical formatter — no config, no debates"
                ],
                [
                  "`zig build`",
                  "Project build system driven by `build.zig`"
                ],
                [
                  "`zig init`",
                  "Scaffold a new project (`build.zig` + `src/main.zig`)"
                ],
                [
                  "`zig fetch <url>`",
                  "Fetch and cache a package dependency"
                ],
                [
                  "`zig cc` / `zig c++`",
                  "Drop-in C/C++ compiler with free cross-compilation"
                ],
                [
                  "`zig translate-c file.c`",
                  "Convert C source to Zig"
                ],
                [
                  "`zig env` / `zig targets`",
                  "Show cache/lib paths / list supported compile targets"
                ]
              ]
            },
            {
              "kind": "table",
              "headers": [
                "Flag",
                "Effect"
              ],
              "rows": [
                [
                  "`-O Debug|ReleaseSafe|ReleaseFast|ReleaseSmall`",
                  "Choose the build mode (see below)"
                ],
                [
                  "`-target x86_64-linux-musl`",
                  "Cross-compile for any `arch-os-abi`"
                ],
                [
                  "`-mcpu <name>`",
                  "Select target CPU and feature set"
                ]
              ]
            },
            {
              "kind": "code",
              "lang": "sh",
              "title": "Cross-compile a static binary — no extra toolchain",
              "code": "zig build-exe main.zig -O ReleaseFast -target x86_64-linux-musl\n./main"
            }
          ]
        },
        {
          "id": "getting-started-modes",
          "title": "Build Modes",
          "summary": "Four modes trade compile time, runtime speed, size and safety checks.",
          "keywords": [
            "debug",
            "releasesafe",
            "releasefast",
            "releasesmall",
            "optimization",
            "safety",
            "undefined behaviour",
            "build mode"
          ],
          "blocks": [
            {
              "kind": "table",
              "headers": [
                "Mode",
                "Compile time",
                "Runtime",
                "Safety checks",
                "Notes"
              ],
              "rows": [
                [
                  "`Debug`",
                  "fastest",
                  "slowest",
                  "all",
                  "Default for `zig run`, `zig test`"
                ],
                [
                  "`ReleaseSafe`",
                  "slower",
                  "optimized",
                  "all",
                  "Panics on overflow, out-of-bounds, …"
                ],
                [
                  "`ReleaseFast`",
                  "slower",
                  "fastest",
                  "**off**",
                  "Violations become undefined behaviour"
                ],
                [
                  "`ReleaseSmall`",
                  "slower",
                  "small, less optimized",
                  "mostly off",
                  "Optimizes for binary size"
                ]
              ]
            },
            {
              "kind": "warn",
              "title": "ReleaseFast removes the guard rails",
              "content": "In `ReleaseFast`, integer overflow or an out-of-bounds access is **undefined behaviour** — it may silently corrupt memory instead of panicking. Ship `ReleaseSafe` unless you truly need the last few percent of performance."
            },
            {
              "kind": "code",
              "lang": "sh",
              "title": "Typical workflow",
              "code": "zig run app.zig                     # iterate in Debug\nzig test app.zig                    # tests in Debug\nzig build-exe app.zig -O ReleaseSafe  # ship with safety checks\nzig build -Doptimize=ReleaseFast      # via build.zig"
            }
          ]
        },
        {
          "id": "getting-started-comments",
          "title": "Comments & Docs",
          "summary": "Line comments only — plus two flavours of doc comments for autodoc.",
          "keywords": [
            "comments",
            "doc comment",
            "top-level doc",
            "autodoc",
            "documentation",
            "zig fmt"
          ],
          "blocks": [
            {
              "kind": "code",
              "lang": "zig",
              "code": "//! Top-level doc comment: describes the whole FILE.\n\n/// Doc comment for the declaration that follows.\n/// Rendered by zig build-docs / autodoc.\npub fn add(a: i32, b: i32) i32 {\n    return a + b; // plain line comment\n}\n\n// There are no /* block comments */ in Zig."
            },
            {
              "kind": "list",
              "items": [
                "`//` — the only comment. There are **no block comments**.",
                "`///` — doc comment; attaches to the declaration below it and shows up in generated docs.",
                "`//!` — file-level doc comment; must appear at the very top of the file."
              ]
            }
          ]
        },
        {
          "id": "getting-started-anatomy",
          "title": "File Anatomy & Imports",
          "summary": "Every file is a namespace; `@import` wires them together; `pub` controls visibility.",
          "keywords": [
            "import",
            "pub",
            "namespace",
            "root file",
            "main.zig",
            "module",
            "visibility"
          ],
          "blocks": [
            {
              "kind": "list",
              "items": [
                "`@import(\"std\")` resolves to the standard library; `@import(\"math.zig\")` loads a file **relative to the current file**.",
                "`pub` makes a declaration visible to importers — everything else stays private to the file.",
                "Declaration order is irrelevant: top-level decls are lazily analyzed, so forward references just work.",
                "Every file is implicitly a **struct / namespace**; `@This()` names it from inside.",
                "The entry point lives in the **root source file** — `main.zig` when you `zig run main.zig`, or the root module declared in `build.zig`."
              ]
            },
            {
              "kind": "compare",
              "left": {
                "title": "main.zig",
                "lang": "zig",
                "code": "const std = @import(\"std\");\nconst math = @import(\"math.zig\");\n\npub fn main() !void {\n    const sum = math.add(2, 3);\n    std.debug.print(\"{d}\\n\", .{sum});\n}"
              },
              "right": {
                "title": "math.zig",
                "lang": "zig",
                "code": "const magic = 42; // private to this file\n\npub fn add(a: i32, b: i32) i32 {\n    return a + b;\n}\n\n// Order never matters:\npub const two = add(1, 1);"
              },
              "note": "Non-`pub` decls like `magic` are invisible to importers — the file boundary is a hard visibility wall."
            }
          ]
        }
      ]
    },
    {
      "id": "basics",
      "title": "Language Basics",
      "icon": "FileCode",
      "blurb": "const vs var, statements vs expressions, labeled blocks, `undefined` and the comptime mindset.",
      "entries": [
        {
          "id": "basics-vars",
          "title": "Variables: const & var",
          "ziglings": "003, 051",
          "summary": "Two keywords, strict usage rules, and compile errors that keep code honest.",
          "keywords": [
            "const",
            "var",
            "unused",
            "discard",
            "shadowing",
            "type inference",
            "mutate"
          ],
          "blocks": [
            {
              "kind": "text",
              "content": "`const` is immutable, `var` is mutable. Types are usually inferred; write them when it matters: `const x: u32 = 1;`."
            },
            {
              "kind": "code",
              "lang": "zig",
              "code": "const answer: u32 = 42;      // immutable, explicit type\nvar counter = answer;        // mutable, type inferred (u32)\ncounter += 1;                // ok: vars may be reassigned\n\nfn sum(items: []const u32) u32 {\n    var total: u32 = 0;\n    for (items) |item| total += item;\n    return total;\n}"
            },
            {
              "kind": "table",
              "headers": [
                "Form",
                "Meaning"
              ],
              "rows": [
                [
                  "`const x = v;`",
                  "Immutable binding"
                ],
                [
                  "`var x = v;`",
                  "Mutable binding"
                ],
                [
                  "`_ = x;`",
                  "Explicitly discard — silences the unused error"
                ],
                [
                  "`_ = &x;`",
                  "Take and discard the address — also counts as a use"
                ]
              ]
            },
            {
              "kind": "warn",
              "title": "Compile errors you will hit on day one",
              "content": "Unused local variables **and parameters** are errors — discard them with `_ = x;`. A `var` that is never mutated is an error. So is **shadowing** an identifier from an outer scope."
            }
          ]
        },
        {
          "id": "basics-statements",
          "title": "Statements ≠ Expressions",
          "summary": "Assignment is a void statement; control flow yields values instead.",
          "keywords": [
            "expression",
            "statement",
            "ternary",
            "increment",
            "assignment",
            "and",
            "or",
            "operators"
          ],
          "blocks": [
            {
              "kind": "text",
              "content": "Zig separates the two strictly: assignment returns `void`, so `x = y = 0` is impossible. There is no `++` / `--` (use `x += 1`) and no ternary — because `if` **is** an expression."
            },
            {
              "kind": "code",
              "lang": "zig",
              "code": "const a = 3;\nconst b = 7;\n\n// const c = a = b;            // ERROR: assignment yields void\n// a++;                        // ERROR: no ++ — use a += 1\n// const max = a > b ? a : b;  // ERROR: no ternary operator\n\nconst max = if (a > b) a else b;         // 7\nconst ok = (a < b) and (b != 0);         // and / or / ! — never && || !\n\nconst both = blk: {                      // blocks are expressions too\n    const squared = a * a;\n    break :blk squared + b;              // yields 16\n};"
            }
          ]
        },
        {
          "id": "basics-blocks",
          "title": "Blocks & Scope",
          "summary": "Labeled blocks are inline scopes that can yield a value.",
          "keywords": [
            "block",
            "scope",
            "labeled block",
            "break",
            "label",
            "yield value"
          ],
          "blocks": [
            {
              "kind": "text",
              "content": "Declarations live inside their block's scope. Any block can be given a **label**; `break :label value;` exits it with a result — Zig's tool for computed fallback chains and early exits from a scope."
            },
            {
              "kind": "code",
              "lang": "zig",
              "code": "const n: i32 = -4;\n\nconst magnitude = abs: {\n    if (n >= 0) break :abs n;   // leave early with a value\n    break :abs -n;\n};                              // magnitude == 4\n\n// Reads like an inline function:\nconst squares = first_three: {\n    var buf: [3]u32 = undefined;\n    for (0..3) |i| buf[i] = @intCast((i + 1) * (i + 1));\n    break :first_three buf;\n};"
            },
            {
              "kind": "text",
              "content": "Labeled blocks replace many nested-`if` and `switch`-fallback patterns — and pair naturally with `errdefer`-style control flow later on."
            }
          ]
        },
        {
          "id": "basics-undefined",
          "title": "undefined",
          "ziglings": "050",
          "summary": "Opt out of initialization — the 0xAA fill and the compiler both have opinions.",
          "keywords": [
            "undefined",
            "initialization",
            "0xaa",
            "deferred init",
            "performance",
            "buffer"
          ],
          "blocks": [
            {
              "kind": "text",
              "content": "`var x: u32 = undefined;` means **no initialization**. In Debug and ReleaseSafe the bytes are filled with `0xAA` so stale reads are obvious; in the other modes they are garbage. Useful for deferred init and buffers you will fully overwrite."
            },
            {
              "kind": "code",
              "lang": "zig",
              "code": "var buf: [64]u8 = undefined;   // uninit 64 bytes\nbuf[0] = 'H';\nbuf[1] = 'i';\n\nvar total: u32 = undefined;    // deferred init\ntotal = 10;\ntotal += 5;                    // safe: written before any read"
            },
            {
              "kind": "warn",
              "title": "undefined is not a value",
              "content": "**Reading** a location that was never written is undefined behaviour — the `0xAA` fill is a debugging aid, not a check. `undefined` may only initialize a `var`; a `const` needs a real value."
            }
          ]
        },
        {
          "id": "basics-comptime-kw",
          "title": "The comptime Keyword",
          "summary": "Anything can run at compile time — that is how Zig does generics.",
          "keywords": [
            "comptime",
            "compile time",
            "generics",
            "comptime_int",
            "type values",
            "metaprogramming"
          ],
          "blocks": [
            {
              "kind": "text",
              "content": "Zig has no macros and no template engine: the language itself is the metaprogramming layer. `comptime` marks parameters, variables or blocks that must be known at compile time — and types themselves are values of type `type`."
            },
            {
              "kind": "code",
              "lang": "zig",
              "code": "fn Buffer(comptime T: type, comptime size: usize) type {\n    return struct {\n        items: [size]T,\n        len: usize = 0,\n    };\n}\n\nconst Buf8 = Buffer(u8, 8);     // a brand-new type...\nconst Buf32 = Buffer(u8, 32);   // ...generated at compile time\n\nconst n = 40 + 2;               // comptime_int: arbitrary-precision math"
            },
            {
              "kind": "list",
              "items": [
                "`comptime_int` grows arbitrarily — integer literals have no fixed width until assigned to a typed `const` / `var`.",
                "Every instantiation compiles **fresh code**, including whatever parts of `std` it touches — that is why the whole std library feels like it is compiled per-use.",
                "This is Zig's answer to C macros and C++ templates: ordinary code, evaluated at compile time."
              ]
            }
          ]
        },
        {
          "id": "basics-identifiers",
          "title": "Identifiers & Literals",
          "ziglings": "079",
          "summary": "snake_case, raw identifiers for keywords, and every numeric literal prefix.",
          "keywords": [
            "identifiers",
            "snake_case",
            "raw identifier",
            "literals",
            "binary",
            "hex",
            "unicode",
            "character"
          ],
          "blocks": [
            {
              "kind": "code",
              "lang": "zig",
              "code": "const max_value = 1_000_000;     // snake_case — zig fmt enforces the style\nconst flags = 0b1010;            // binary\nconst perms = 0o755;             // octal\nconst mask = 0xFF;               // hex\nconst big = 1_000.5;             // _ works in floats too\n\nconst letter = 'a';              // char literal (comptime_int)\nconst newline = '\\n';\nconst emoji: u21 = '\\u{1F600}'; // unicode code point\n\nconst @\"if\" = 1;                 // raw identifier for keywords...\nconst @\"weird name\" = 2;         // ...or any unusual spelling"
            },
            {
              "kind": "table",
              "headers": [
                "Literal",
                "Kind"
              ],
              "rows": [
                [
                  "`123`, `1_000_000`",
                  "Decimal integer, `_` as separator"
                ],
                [
                  "`0b1010`, `0o755`, `0xFF`",
                  "Binary / octal / hex integer"
                ],
                [
                  "`1_000.5`, `1e10`",
                  "Float"
                ],
                [
                  "`'a'`, `'\\n'`, `'\\u{1F600}'`",
                  "Character — a `comptime_int`"
                ],
                [
                  "`@\"if\"`",
                  "Raw identifier — escapes keywords and spaces"
                ]
              ]
            }
          ]
        }
      ]
    },
    {
      "id": "primitive-types",
      "title": "Primitive Types",
      "icon": "Hash",
      "blurb": "Integers of any width, IEEE floats, bool / void / noreturn — and exactly which values coerce.",
      "entries": [
        {
          "id": "types-integers",
          "title": "Integers",
          "ziglings": "059",
          "summary": "Any bit width you want, plus the special arbitrary-precision comptime_int.",
          "keywords": [
            "integers",
            "u8",
            "usize",
            "arbitrary width",
            "comptime_int",
            "char literal"
          ],
          "blocks": [
            {
              "kind": "table",
              "headers": [
                "Type",
                "Notes"
              ],
              "rows": [
                [
                  "`u8 i8 u16 i16 u32 i32 u64 i64 u128 i128`",
                  "The fixed-width family"
                ],
                [
                  "`usize` / `isize`",
                  "Pointer-sized; required for indexing"
                ],
                [
                  "`u7`, `i47`, `u65535`",
                  "Arbitrary widths — any `uN` / `iN` with N ≤ 65535"
                ],
                [
                  "`u0`",
                  "Zero-bit type; its only value is `0`"
                ],
                [
                  "`comptime_int`",
                  "Arbitrary precision — the default type of integer literals"
                ]
              ]
            },
            {
              "kind": "code",
              "lang": "zig",
              "code": "const a: u8 = 255;             // fits exactly\nconst b = 300;                 // comptime_int — no width yet\nconst c: u16 = b;              // ok: 300 fits u16\n// const d: u8 = b;            // compile error: 300 does not fit u8\nconst e: u9 = 511;             // odd widths are fine\nconst byte = 'A';              // char literal coerces into u8 and friends"
            }
          ]
        },
        {
          "id": "types-overflow",
          "title": "Overflow Behavior",
          "summary": "Panics, UB or wraps — plus wrapping and saturating operator variants.",
          "keywords": [
            "overflow",
            "wrapping",
            "saturating",
            "addwithoverflow",
            "panic",
            "undefined behaviour",
            "shl"
          ],
          "blocks": [
            {
              "kind": "table",
              "headers": [
                "Build mode",
                "On overflow"
              ],
              "rows": [
                [
                  "`Debug` / `ReleaseSafe`",
                  "Panic (safety-checked)"
                ],
                [
                  "`ReleaseFast`",
                  "Undefined behaviour"
                ],
                [
                  "`ReleaseSmall`",
                  "Wraps (well-defined)"
                ]
              ]
            },
            {
              "kind": "table",
              "headers": [
                "Tool",
                "Meaning"
              ],
              "rows": [
                [
                  "`+%` `-%` `*%`",
                  "Wrapping add / sub / mul"
                ],
                [
                  "`+|` `-|` `*|`",
                  "Saturating add / sub / mul"
                ],
                [
                  "`<<|`",
                  "Saturating shift left"
                ],
                [
                  "`@addWithOverflow(a, b)`",
                  "Returns a tuple: result + overflow bit"
                ]
              ]
            },
            {
              "kind": "code",
              "lang": "zig",
              "code": "const big: u8 = 250;\n\nconst wrapped = big +% 10;          // 4\nconst saturated = big +| 10;        // 255\nconst shifted = @as(u8, 1) <<| 9;   // 255 — saturates instead of UB\n\nconst pair = @addWithOverflow(big, @as(u8, 10));\n// pair[0] == 4, pair[1] == 1 (overflowed)"
            }
          ]
        },
        {
          "id": "types-floats",
          "title": "Floats",
          "ziglings": "060",
          "summary": "IEEE floats up to 128 bits — conversions stay explicit.",
          "keywords": [
            "floats",
            "f32",
            "f64",
            "floatfromint",
            "intfromfloat",
            "floatcast",
            "nan",
            "sqrt"
          ],
          "blocks": [
            {
              "kind": "table",
              "headers": [
                "Type",
                "Notes"
              ],
              "rows": [
                [
                  "`f16` `f32` `f64` `f80` `f128`",
                  "IEEE-754 widths; hardware support varies"
                ],
                [
                  "`comptime_float`",
                  "The type of float literals at compile time"
                ]
              ]
            },
            {
              "kind": "text",
              "content": "Integers and floats never mix implicitly in arithmetic: `1 + x` with `x: f32` is an error — write `@as(f32, 1) + x`. Convert explicitly with `@floatFromInt`, `@intFromFloat` (out-of-range is undefined behaviour; safety-checked panic in safe modes) and `@floatCast`. There is **no NaN literal** — use `std.math.nan(f64)`."
            },
            {
              "kind": "code",
              "lang": "zig",
              "code": "const std = @import(\"std\");\n\nconst pi: f64 = 3.14159;\nconst root = @sqrt(pi);                 // runtime and comptime\nconst smaller: f32 = @floatCast(pi);    // f64 -> f32\nconst n: i32 = @intFromFloat(3.99);     // truncates toward zero: 3\nconst f: f64 = @floatFromInt(7);        // 7.0\nconst not_a_number = std.math.nan(f64);"
            },
            {
              "kind": "warn",
              "title": "0.16: small integers now coerce into floats",
              "content": "Since **0.16**, small integer widths coerce into larger float types (e.g. `u8` into `f64`) where every value is exactly representable — no cast needed."
            }
          ]
        },
        {
          "id": "types-void-bool",
          "title": "bool, void & noreturn",
          "summary": "Three tiny types that shape control flow.",
          "keywords": [
            "bool",
            "void",
            "noreturn",
            "unreachable",
            "zero-bit",
            "unit type"
          ],
          "blocks": [
            {
              "kind": "code",
              "lang": "zig",
              "code": "const std = @import(\"std\");\n\nconst ready: bool = true;\nconst check = ready and !ready;   // and / or / ! only\n\nconst unit: void = {};            // zero-bit: takes no memory\n\nfn log(msg: []const u8) void {\n    std.debug.print(\"{s}\\n\", .{msg});\n}\n\nfn crash() noreturn {             // never returns\n    unreachable;                  // panics — or: while (true) {}\n}"
            },
            {
              "kind": "list",
              "items": [
                "`bool` — `true` / `false`, combined with the `and` / `or` / `!` keywords.",
                "`void` — the unit type; its only value is `{}`. Zero-bit types (`void`, `u0`, empty structs) occupy **no memory**.",
                "`noreturn` — the type of `unreachable`, panics and infinite loops without `break`; useful as a return type and in `else` prongs."
              ]
            }
          ]
        },
        {
          "id": "types-coercions",
          "title": "Implicit Coercions",
          "ziglings": "061",
          "summary": "Widening is free; narrowing must be explicit and runtime-checked.",
          "keywords": [
            "coercion",
            "intcast",
            "widening",
            "narrowing",
            "optional",
            "error union",
            "string literal"
          ],
          "blocks": [
            {
              "kind": "table",
              "headers": [
                "From",
                "To",
                "Implicit?"
              ],
              "rows": [
                [
                  "`u8`",
                  "`u32` (wider, same sign)",
                  "**yes**"
                ],
                [
                  "`u32`",
                  "`u8` (narrower)",
                  "no — `@intCast`"
                ],
                [
                  "`i32`",
                  "`u64` (sign change)",
                  "no — signs must match"
                ],
                [
                  "`[N]T`",
                  "`[]const T`",
                  "**yes**"
                ],
                [
                  "string literal `*const [N:0]u8`",
                  "`[]const u8` / `[*:0]const u8` / `*const [N]u8`",
                  "**yes**"
                ],
                [
                  "`*T`",
                  "`?*T`",
                  "**yes** (same size)"
                ],
                [
                  "`T`",
                  "`?T`",
                  "**yes**"
                ],
                [
                  "`T`",
                  "`E!T`",
                  "**yes**"
                ],
                [
                  "error set",
                  "superset error set",
                  "**yes**"
                ],
                [
                  "`comptime_int` / `comptime_float`",
                  "any int / float that fits",
                  "**yes**"
                ]
              ]
            },
            {
              "kind": "warn",
              "title": "Narrowing never happens silently",
              "content": "`const small: u8 = big;` with `big: u32` is a **compile error**. Make it explicit with `@intCast(big)` — runtime-checked, panics in safe modes when the value does not fit (undefined behaviour in `ReleaseFast`)."
            }
          ]
        },
        {
          "id": "types-reflection",
          "title": "Type Reflection",
          "summary": "Introspect any type at compile time via @typeInfo.",
          "keywords": [
            "typeof",
            "typename",
            "sizeof",
            "typeinfo",
            "reflection",
            "comptime",
            "fields"
          ],
          "blocks": [
            {
              "kind": "code",
              "lang": "zig",
              "code": "const std = @import(\"std\");\n\nconst T = @TypeOf(42);             // comptime_int\nconst label = @typeName(u32);      // \"u32\"\nconst size = @sizeOf(u64);         // 8 — also @bitSizeOf / @alignOf\n\nconst info = @typeInfo(struct { id: u32, name: []const u8 });\nconst fields = info.@\"struct\".fields;   // keyword tags need @\"\" quoting\n\ncomptime {\n    for (fields) |f| std.debug.print(\"{s}\\n\", .{f.name});  // id, name\n}"
            },
            {
              "kind": "list",
              "items": [
                "`@typeInfo(T)` returns a **tagged union**; tags are lowercase snake_case: `.int`, `.pointer`, `.@\"struct\"`, …",
                "Each struct field exposes `.name`, `.type`, `.default_value` and `.alignment`.",
                "**0.16:** `@Type` was removed — build types with individual builtins instead: `@Struct`, `@Enum`, `@Pointer`, `@Array`, …"
              ]
            }
          ]
        }
      ]
    },
    {
      "id": "arrays-slices-strings",
      "title": "Arrays, Slices & Strings",
      "icon": "Braces",
      "blurb": "Fixed arrays, runtime slices, UTF-8 byte strings, sentinel terminators and SIMD vectors.",
      "entries": [
        {
          "id": "arr-arrays",
          "title": "Arrays",
          "ziglings": "004–005",
          "summary": "Fixed length, comptime-known, value semantics.",
          "keywords": [
            "array",
            "len",
            "repeat",
            "nested",
            "for loop",
            "value semantics"
          ],
          "blocks": [
            {
              "kind": "code",
              "lang": "zig",
              "code": "const std = @import(\"std\");\n\nconst fixed = [3]u8{ 1, 2, 3 };\nconst inferred = [_]u8{ 4, 5, 6 };     // length from the initializer\nconst filled = [1]u8{0xAA} ** 16;      // ** repeats: 16 bytes of 0xAA\nconst grid = [2][3]u8{\n    .{ 1, 2, 3 },                      // nested arrays\n    .{ 4, 5, 6 },\n};\n\nfor (inferred) |item| std.debug.print(\"{d}\\n\", .{item});\nfor (fixed, 0..) |item, i| std.debug.print(\"[{d}]={d}\\n\", .{ i, item });"
            },
            {
              "kind": "list",
              "items": [
                "`[N]T` — the length is part of the type and **comptime-known**; `fixed.len` is a compile-time constant.",
                "Arrays are **values**: assignment copies, and passing to a function copies.",
                "`for (arr) |item|` iterates values; `for (arr, 0..) |item, i|` adds an index."
              ]
            }
          ]
        },
        {
          "id": "arr-slices",
          "title": "Slices",
          "ziglings": "052–053",
          "summary": "A pointer plus runtime length — the workhorse view type.",
          "keywords": [
            "slice",
            "len",
            "ptr",
            "bounds check",
            "slicing",
            "view"
          ],
          "blocks": [
            {
              "kind": "code",
              "lang": "zig",
              "code": "var data = [_]u32{ 1, 2, 3, 4, 5 };\n\nconst window: []u32 = data[1..4];      // { 2, 3, 4 }\nconst rest = data[2..];                // open-ended: to the end\nconst whole: []const u32 = &data;      // [N]T coerces to []const T\n\nfn sum(items: []const u32) u32 {\n    var total: u32 = 0;\n    for (items) |item| total += item;  // len lives at runtime\n    return total;\n}"
            },
            {
              "kind": "list",
              "items": [
                "`[]T` is a mutable view, `[]const T` read-only. A slice is a **pointer + runtime `.len`**; `slice.ptr` hands out the raw pointer.",
                "Slicing a slice works: `rest[1..2]`. Bounds are **checked at runtime** — out of range panics in safe modes.",
                "Multi-object loop: `for (as, bs) |a, b|` iterates two sequences in lockstep — equal lengths are a runtime-checked requirement."
              ]
            }
          ]
        },
        {
          "id": "arr-strings",
          "title": "Strings",
          "ziglings": "006–007",
          "summary": "No string type — just []const u8 over UTF-8 bytes.",
          "keywords": [
            "strings",
            "utf-8",
            "mem.eql",
            "concat",
            "allocprint",
            "unicode",
            "string literal"
          ],
          "blocks": [
            {
              "kind": "text",
              "content": "A string literal has type `*const [N:0]u8` (null-terminated at compile time). The canonical **string type** is `[]const u8` — a byte slice, not null-terminated, holding arbitrary UTF-8. There is no built-in string type and **no concat operator**: build strings with an allocator."
            },
            {
              "kind": "code",
              "lang": "zig",
              "code": "const std = @import(\"std\");\n\nconst literal = \"héllo\";                       // *const [6:0]u8\nconst view: []const u8 = literal;              // canonical string type\nconst same = std.mem.eql(u8, \"abc\", \"abc\");    // byte compare: true\nconst starts = std.mem.startsWith(u8, \"hello!\", \"hell\");\nconst valid = std.unicode.utf8ValidateSlice(\"héllo\");\n\n// Iterate codepoints:\nvar it = std.unicode.Utf8View.initUnchecked(\"héllo\").iterator();\nwhile (it.nextCodepoint()) |cp| { _ = cp; }"
            },
            {
              "kind": "code",
              "lang": "zig",
              "title": "Concatenation needs an allocator",
              "code": "pub fn main(init: std.process.Init) !void {\n    const greeting = try std.fmt.allocPrint(init.arena, \"hi, {s}!\", .{\"zig\"});\n    try std.Io.File.stdout().writeStreamingAll(init.io, greeting);\n}"
            },
            {
              "kind": "table",
              "headers": [
                "Expression",
                "Type / result"
              ],
              "rows": [
                [
                  "`\"hi\"`",
                  "`*const [2:0]u8`"
                ],
                [
                  "`\"hi\"` coerced",
                  "`[]const u8`, `[*:0]const u8`, `*const [2]u8`"
                ],
                [
                  "`'x'`",
                  "Char literal — a `comptime_int`"
                ],
                [
                  "`std.mem.concat(alloc, u8, .{a, b})`",
                  "New heap buffer from parts"
                ]
              ]
            }
          ]
        },
        {
          "id": "arr-multiline",
          "title": "Multiline String Literals",
          "summary": "Every line starts with \\\\ — no escapes, ever.",
          "keywords": [
            "multiline string",
            "literal",
            "escape",
            "raw text",
            "css"
          ],
          "blocks": [
            {
              "kind": "code",
              "lang": "zig",
              "code": "const css =\n    \\\\body {\n    \\\\  color: red;\n    \\\\};"
            },
            {
              "kind": "list",
              "items": [
                "Each line starts with `\\\\`; line breaks and spacing are preserved **exactly**.",
                "No escape sequences inside — `\\\\n` is a literal backslash followed by `n`.",
                "The final line contributes no trailing newline."
              ]
            }
          ]
        },
        {
          "id": "arr-sentinels",
          "title": "Sentinel-Terminated",
          "ziglings": "076–078",
          "summary": "Terminators baked into the type — how Zig talks to C.",
          "keywords": [
            "sentinel",
            "null-terminated",
            "c string",
            "mem.span",
            "allocsentinel",
            "buffer"
          ],
          "blocks": [
            {
              "kind": "code",
              "lang": "zig",
              "code": "const std = @import(\"std\");\n\nconst c_str: [*:0]const u8 = \"hello\";   // many-item + terminator\n\nvar buf: [16:0]u8 = undefined;          // array with a slot for the 0\nbuf[0] = 'o';\nbuf[1] = 'k';\nbuf[2] = 0;\n\nconst s: [:0]u8 = buf[0..2 :0];         // slice that keeps the sentinel\nconst span: []const u8 = std.mem.span(c_str);   // scan to terminator"
            },
            {
              "kind": "list",
              "items": [
                "`[N:0]u8` — sentinel-terminated array; `[:0]u8` — slice whose memory past the end is the sentinel value.",
                "`std.mem.span` / `std.mem.spanZ` turn a `[*:0]u8` into a length-aware slice — the bridge from C strings.",
                "`allocator.allocSentinel(u8, n, 0)` allocates a sentinel-terminated buffer.",
                "Common uses: C interop (`char *`), in-place formatting buffers."
              ]
            }
          ]
        },
        {
          "id": "arr-vectors",
          "title": "Vectors (SIMD)",
          "ziglings": "112",
          "summary": "Fixed-width SIMD lanes with element-wise operators.",
          "keywords": [
            "vector",
            "simd",
            "splat",
            "reduce",
            "select",
            "lanes",
            "bitcast"
          ],
          "blocks": [
            {
              "kind": "code",
              "lang": "zig",
              "code": "const a: @Vector(4, f32) = .{ 1.0, 2.0, 3.0, 4.0 };\nconst b: @Vector(4, f32) = @splat(4, 2.0);   // 0.14+: length first\n\nconst sum = a + b;                 // element-wise: { 3, 4, 5, 6 }\nconst total = @reduce(.Add, sum);  // horizontal: 18.0\nconst pick = @select(f32, a > b, a, b);   // per-lane max here"
            },
            {
              "kind": "list",
              "items": [
                "`@Vector(len, T)` — the length must be comptime-known; lanes are bool, integer or float.",
                "Operators apply **element-wise**; `@reduce` collapses to a scalar; `@select` picks per lane; `std.simd` has extra helpers.",
                "`@splat(len, scalar)` broadcasts a scalar (length-first signature since **0.14**)."
              ]
            },
            {
              "kind": "warn",
              "title": "0.16: arrays and vectors no longer coerce in memory",
              "content": "Since **0.16**, `[4]f32` and `@Vector(4, f32)` are kept strictly apart — convert between them explicitly with `@bitCast`."
            }
          ]
        },
        {
          "id": "arr-embed",
          "title": "Compile-Time Data: @embedFile",
          "summary": "Bake files into the binary as comptime-known bytes.",
          "keywords": [
            "embedfile",
            "assets",
            "compile time",
            "binary",
            "static data",
            "std.fs"
          ],
          "blocks": [
            {
              "kind": "code",
              "lang": "zig",
              "code": "const std = @import(\"std\");\n\nconst logo = @embedFile(\"assets/logo.svg\");  // *const [N:0]u8\nconst size = logo.len;                       // comptime-known\n\npub fn main(init: std.process.Init) !void {\n    try std.Io.File.stdout().writeStreamingAll(init.io, logo);\n}"
            },
            {
              "kind": "list",
              "items": [
                "The path is relative to the source file and must stay inside the module — the file becomes part of your binary.",
                "Result type is `*const [N:0]u8`: a comptime-known, sentinel-terminated byte array.",
                "For **runtime** files, use the file-system APIs (`std.fs`, `Io.Dir`) instead.",
                "Works naturally with `zig build` modules — assets can also come from dependency packages."
              ]
            }
          ]
        }
      ]
    },
    {
      "id": "pointers",
      "title": "Pointers",
      "icon": "Crosshair",
      "blurb": "Six pointer spellings, manual arithmetic, casts and alignment discipline — non-null unless you ask.",
      "entries": [
        {
          "id": "ptr-basics",
          "title": "Taking Addresses",
          "ziglings": "039–040",
          "summary": "& to take, .* to dereference — parameters are always values.",
          "keywords": [
            "pointer",
            "address",
            "dereference",
            "by value",
            "mutate",
            "const pointer"
          ],
          "blocks": [
            {
              "kind": "code",
              "lang": "zig",
              "code": "var score: u32 = 100;\n\nconst p: *u32 = &score;        // pointer to score\np.* += 10;                     // deref-write: score == 110\nconst q: *const u32 = &score;  // read-only pointer\nconst v = q.*;                 // deref-read: 110\n\nfn bump(n: *u32) void {        // to mutate the caller's value,\n    n.* += 1;                  // take a pointer parameter\n}\nbump(&score);                  // score == 111"
            },
            {
              "kind": "text",
              "content": "Everything is **passed by value** and parameters are immutable — there is no implicit by-reference. To mutate a caller's value (or avoid copying a big struct) pass a `*T`; pass `*const T` for read-only access."
            }
          ]
        },
        {
          "id": "ptr-kinds",
          "title": "Pointer Kinds",
          "ziglings": "041–044",
          "summary": "Six pointer spellings — size, length and nullability differ.",
          "keywords": [
            "pointer kinds",
            "single item",
            "many-item",
            "slice",
            "c pointer",
            "optional pointer",
            "sentinel"
          ],
          "blocks": [
            {
              "kind": "table",
              "headers": [
                "Type",
                "Points to",
                "Length",
                "Nullable",
                "Notes"
              ],
              "rows": [
                [
                  "`*T`",
                  "one item",
                  "1",
                  "no",
                  "Non-null, naturally aligned"
                ],
                [
                  "`*const T`",
                  "one item",
                  "1",
                  "no",
                  "Pointee is read-only"
                ],
                [
                  "`[*]T`",
                  "many items",
                  "unknown",
                  "no",
                  "No `.len` — arithmetic allowed"
                ],
                [
                  "`[*:0]T`",
                  "many items",
                  "sentinel-delimited",
                  "no",
                  "C-string style"
                ],
                [
                  "`[]T` / `[]const T`",
                  "many items",
                  "runtime `.len`",
                  "no",
                  "Slice: pointer + length"
                ],
                [
                  "`[*c]T`",
                  "many or one",
                  "unknown",
                  "yes",
                  "C pointer from `translate-c`; coerces both ways"
                ],
                [
                  "`?*T`",
                  "one item",
                  "1",
                  "yes",
                  "Same size as `*T` — null reuses the zero bits"
                ]
              ]
            },
            {
              "kind": "text",
              "content": "A non-optional pointer can **never** be null, and `?*T` costs no extra memory — Zig reserves the all-zero-bits pattern as `null`. Prefer slices over bare pointers whenever a length is meaningful."
            }
          ]
        },
        {
          "id": "ptr-arithmetic",
          "title": "Pointer Arithmetic",
          "ziglings": "054",
          "summary": "Reserved for many-item pointers — slices lend you their `.ptr`.",
          "keywords": [
            "pointer arithmetic",
            "many-item pointer",
            "ptr",
            "intfromptr",
            "ptrfromint",
            "unsafe"
          ],
          "blocks": [
            {
              "kind": "code",
              "lang": "zig",
              "code": "var data = [_]u32{ 10, 20, 30, 40 };\n\nconst p: [*]u32 = &data;      // many-item pointer\nconst second = p + 1;         // arithmetic: only on [*]T\nsecond[0] = 99;               // data[1] == 99 — no bounds check\n\nconst slice: []u32 = data[0..4];\nconst base = slice.ptr;       // same [*]u32\nconst addr: usize = @intFromPtr(base);\nconst back: [*]u32 = @ptrFromInt(addr);"
            },
            {
              "kind": "warn",
              "title": "Manual, unguarded, yours",
              "content": "`[*]T` has **no length**, so indexing it skips bounds checks. In most code, slicing (`p[0..n]`) or keeping a `[]T` is the safer — and equally fast — choice."
            }
          ]
        },
        {
          "id": "ptr-casts",
          "title": "Pointer Casts",
          "summary": "One builtin per job — and alignment is your responsibility.",
          "keywords": [
            "ptrcast",
            "aligncast",
            "constcast",
            "volatilecast",
            "alignment",
            "reinterpret"
          ],
          "blocks": [
            {
              "kind": "code",
              "lang": "zig",
              "code": "var bytes = [_]u8{ 0x37, 0x13, 0x00, 0x00 };\n\n// Reinterpret the pointee type — here with lowered alignment:\nconst loose: *align(1) const u32 = @ptrCast(&bytes);\n\n// Raise alignment: you must guarantee it is truly aligned:\nconst strict: *const u32 = @alignCast(loose);\n\nconst frozen: []const u8 = &bytes;\nconst thawed: []u8 = @constCast(frozen);   // remove const"
            },
            {
              "kind": "table",
              "headers": [
                "Builtin",
                "Job"
              ],
              "rows": [
                [
                  "`@ptrCast`",
                  "Change pointee type / pointer width — may only *lower* alignment"
                ],
                [
                  "`@alignCast`",
                  "Raise the alignment — UB if the real alignment is lower"
                ],
                [
                  "`@constCast`",
                  "Remove `const` from a pointer"
                ],
                [
                  "`@volatileCast`",
                  "Remove `volatile` from a pointer"
                ]
              ]
            },
            {
              "kind": "warn",
              "title": "Alignment UB",
              "content": "Casting a pointer to a **higher** alignment than the data actually has is undefined behaviour — misaligned loads can fault or silently corrupt. `@alignCast` is a promise, not a check."
            },
            {
              "kind": "text",
              "content": "**0.16:** explicitly aligned pointer types like `*align(1) u32` are now distinct types from the naturally aligned `*u32` — but they still coerce to each other."
            }
          ]
        },
        {
          "id": "ptr-volatile",
          "title": "volatile & allowzero",
          "summary": "Talking to hardware and the zero address.",
          "keywords": [
            "volatile",
            "mmio",
            "memory mapped io",
            "allowzero",
            "prefetch",
            "hardware register"
          ],
          "blocks": [
            {
              "kind": "code",
              "lang": "zig",
              "code": "const status_reg: *volatile u32 = @ptrFromInt(0x4000_0000);\n\n// Reads are never elided or reordered away:\nwhile ((status_reg.* & 1) == 0) {}   // wait for the ready bit\n\n// Writes are never removed:\nstatus_reg.* = 0x1;"
            },
            {
              "kind": "list",
              "items": [
                "`*volatile T` marks accesses as **observable** — required for memory-mapped I/O and other sneaky memory.",
                "`*allowzero T` permits address `0` as a valid pointer value (rare; needed only for special ABIs).",
                "`@prefetch(ptr, .{})` hints the cache without dereferencing."
              ]
            }
          ]
        },
        {
          "id": "ptr-meta",
          "title": "Pointer Metadata & Slice Patterns",
          "summary": "Inspect pointer types at comptime; turn buffers into slices early.",
          "keywords": [
            "typeinfo",
            "pointer metadata",
            "alignment",
            "child type",
            "sentinel",
            "buffer slice"
          ],
          "blocks": [
            {
              "kind": "code",
              "lang": "zig",
              "code": "var buf: [64]u8 = undefined;\nconst n = 10;\nconst view: []u8 = buf[0..n];    // buffer + count -> slice: the idiom\n\nconst info = @typeInfo([]const u8).pointer;\n// info.size      -> enum: .one / .many / .slice / .c\n// info.is_const  -> bool\n// info.alignment -> comptime_int\n// info.child     -> u8\n// info.sentinel  -> ?u8 (for sentinel pointers)"
            },
            {
              "kind": "text",
              "content": "The golden rule: a raw buffer plus a count should become a slice (`buf[0..n]`) as early as possible — slices carry their length, work with `for`, and keep bounds checks."
            }
          ]
        }
      ]
    },
    {
      "id": "structs",
      "title": "Structs",
      "icon": "Boxes",
      "blurb": "Plain data with namespaces: methods, tuples, destructuring, packed bits and extern layouts.",
      "entries": [
        {
          "id": "struct-decl",
          "title": "Declaring & Initializing",
          "ziglings": "037–038",
          "summary": "Fields, defaults and decl-literal initialization.",
          "keywords": [
            "struct",
            "fields",
            "defaults",
            "init",
            "decl literal",
            "value semantics"
          ],
          "blocks": [
            {
              "kind": "code",
              "lang": "zig",
              "code": "const Point = struct {\n    x: f64 = 0,               // default value\n    y: f64 = 0,\n\n    const origin = Point{ .x = 0, .y = 0 };   // container-level decl\n};\n\nconst a: Point = .{ .x = 1, .y = 2 };   // decl-literal init\nconst b = Point{ .y = 5 };              // full form; x takes the default\nconst c = a;                            // copy — structs are values"
            },
            {
              "kind": "list",
              "items": [
                "Fields may have **defaults**; omitted fields use them.",
                "`.{ ... }` is the anonymous init (a decl literal) — it needs a result type; `Point{ ... }` is the explicit form.",
                "Structs are plain values: assignment and parameter passing **copy**. No inheritance, no constructors — just data plus functions."
              ]
            }
          ]
        },
        {
          "id": "struct-methods",
          "title": "Methods & Namespaces",
          "ziglings": "047–048",
          "summary": "Functions inside a struct — with an explicit self, no magic.",
          "keywords": [
            "methods",
            "self",
            "namespace",
            "this pointer",
            "static",
            "call sugar"
          ],
          "blocks": [
            {
              "kind": "code",
              "lang": "zig",
              "code": "const Vec = struct {\n    x: f64,\n    y: f64,\n\n    fn length(self: Vec) f64 {           // by value: read-only use\n        return @sqrt(self.x * self.x + self.y * self.y);\n    }\n\n    fn scale(self: *Vec, k: f64) void {  // by pointer: mutation\n        self.x *= k;\n        self.y *= k;\n    }\n};\n\nvar v = Vec{ .x = 3, .y = 4 };\nconst len = v.length();      // sugar for Vec.length(v)\nv.scale(2);                  // sugar for Vec.scale(&v, 2)"
            },
            {
              "kind": "list",
              "items": [
                "There is **no implicit `this`**: the first parameter is the receiver, conventionally named `self` (`Vec`, `*Vec` or `*const Vec`).",
                "Method-call syntax `v.scale(2)` is sugar — it auto-takes `&v` when the receiver is a pointer.",
                "Non-method `fn`s and `const`s in the body act as **static** members; `@This()` names the struct itself from inside."
              ]
            }
          ]
        },
        {
          "id": "struct-tuples",
          "title": "Tuples & Anonymous Structs",
          "ziglings": "080–083",
          "summary": "Struct literals without a name, plus 0.14 destructuring.",
          "keywords": [
            "tuple",
            "anonymous struct",
            "destructuring",
            "swap",
            "discard",
            "multi-object for"
          ],
          "blocks": [
            {
              "kind": "code",
              "lang": "zig",
              "code": "const pair = .{ 3, 7 };                 // tuple: fields \"0\", \"1\"\nconst point = .{ .x = 1, .y = 2 };      // anonymous struct\nconst mixed = .{ 1, \"two\", true };      // heterogeneous tuple\nconst first = mixed[0];                 // comptime index\n\nconst [lo, hi] = pair;                  // destructuring (0.14+)\nconst .{ .x = px, .y = py } = point;    // by field name\n\nvar a: u32 = 1;\nvar b: u32 = 2;\n[a, b] = .{ b, a };                     // swap — no tmp needed"
            },
            {
              "kind": "list",
              "items": [
                "`.{ 1, 2 }` is a **tuple** (fields named `\"0\"`, `\"1\"`, …); `.{ .a = 1 }` is an anonymous struct. Both are real struct types.",
                "Destructuring since **0.14**: `const [a, b] = tup;`, `const .{ .x = x } = point;`, `var` variants, and `_` to discard.",
                "Tuples power multi-object loops: `for (as, bs) |a, b|` iterates a tuple of slices.",
                "Without destructuring, a swap needs a temp: `const tmp = a; a = b; b = tmp;`."
              ]
            }
          ]
        },
        {
          "id": "struct-packed",
          "title": "Packed Structs",
          "ziglings": "114–115",
          "summary": "Exact bit layout over a backing integer.",
          "keywords": [
            "packed struct",
            "bits",
            "bitcast",
            "layout",
            "backing integer",
            "flags"
          ],
          "blocks": [
            {
              "kind": "code",
              "lang": "zig",
              "code": "const Flags = packed struct(u16) {\n    visible: bool,      // 1 bit\n    layer: u8,          // 8 bits\n    tag: u7,            // 7 bits\n};                      // 1 + 8 + 7 == 16, no padding\n\nconst f = Flags{ .visible = true, .layer = 3, .tag = 127 };\nconst bits: u16 = @bitCast(f);       // to the backing integer\nconst back: Flags = @bitCast(bits);  // and back"
            },
            {
              "kind": "list",
              "items": [
                "`packed struct(uN)` lays fields out **bit-exactly** over a backing integer — guaranteed in-memory layout, zero padding.",
                "Field widths must add up exactly; convert with `@bitCast`.",
                "**0.16:** pointers are not allowed inside packed structs and packed unions."
              ]
            },
            {
              "kind": "text",
              "content": "**0.16:** packed structs and packed unions can now appear directly as `switch` prong items."
            }
          ]
        },
        {
          "id": "struct-extern",
          "title": "extern Structs",
          "summary": "C-compatible layout for FFI boundaries.",
          "keywords": [
            "extern struct",
            "c abi",
            "layout",
            "ffi",
            "field order"
          ],
          "blocks": [
            {
              "kind": "code",
              "lang": "zig",
              "code": "const Header = extern struct {\n    magic: u32,          // field order is guaranteed\n    width: u32,\n    height: u32,\n    data: [*]const u8,   // pointers are fine\n};"
            },
            {
              "kind": "table",
              "headers": [
                "Kind",
                "Layout",
                "Use for"
              ],
              "rows": [
                [
                  "`struct`",
                  "Compiler's choice — may reorder and pad",
                  "Everything inside Zig-land"
                ],
                [
                  "`packed struct(uN)`",
                  "Exact bits over a backing integer",
                  "Wire formats, hardware registers"
                ],
                [
                  "`extern struct`",
                  "C ABI, field order guaranteed",
                  "Interop with C, syscalls, file formats"
                ]
              ]
            }
          ]
        },
        {
          "id": "struct-fields",
          "title": "Field Introspection",
          "summary": "Access fields by name at runtime; walk them at compile time.",
          "keywords": [
            "field",
            "reflection",
            "offsetof",
            "hasfield",
            "fieldparentptr",
            "meta.fields"
          ],
          "blocks": [
            {
              "kind": "code",
              "lang": "zig",
              "code": "const std = @import(\"std\");\n\nconst User = struct {\n    id: u32,\n    name: []const u8,\n};\n\nconst user = User{ .id = 7, .name = \"ada\" };\n\ncomptime {\n    std.debug.print(\"has id: {}\\n\", .{@hasField(User, \"id\")});\n    std.debug.print(\"offset of name: {d}\\n\", .{@offsetOf(User, \"name\")});\n\n    inline for (std.meta.fields(User)) |f| {\n        std.debug.print(\"{s}\\n\", .{f.name});   // id, name\n    }\n}\n\nconst picked = @field(user, \"name\");           // access by runtime name"
            },
            {
              "kind": "list",
              "items": [
                "`@field(obj, \"name\")` — access a field (or decl) when the name is only known at runtime.",
                "`@hasField(T, \"name\")`, `@offsetOf(T, \"name\")`, and `@fieldParentPtr` for the rare parent-pointer trick.",
                "`std.meta.fields(T)` (or `@typeInfo(T).@\"struct\".fields`) + `inline for` is the workhorse for compile-time iteration."
              ]
            }
          ]
        }
      ]
    },
    {
      "id": "enums-unions",
      "title": "Enums & Unions",
      "icon": "Shuffle",
      "blurb": "Exhaustive tags, payload unions with compiler-tracked active fields, and packed bit views.",
      "entries": [
        {
          "id": "enum-decl",
          "title": "Enums",
          "ziglings": "035–036",
          "summary": "Tags with an optional integer backing — exhaustive by default.",
          "keywords": [
            "enum",
            "tag type",
            "intfromenum",
            "enumfromint",
            "switch",
            "non-exhaustive"
          ],
          "blocks": [
            {
              "kind": "code",
              "lang": "zig",
              "code": "const Color = enum { red, green, blue };\n\nconst Mode = enum(u8) {        // explicit tag type\n    off = 0,\n    on = 1,\n    auto = 255,\n};\n\nconst n = @intFromEnum(Mode.on);     // -> u8 value\nconst m: Mode = @enumFromInt(1);     // checked in safe modes\n\nfn describe(c: Color) []const u8 {\n    return switch (c) {              // must be exhaustive\n        .red => \"stop\",\n        .green => \"go\",\n        .blue => \"chill\",\n    };\n}"
            },
            {
              "kind": "list",
              "items": [
                "Without a backing type, the compiler picks the smallest integer that fits all tags.",
                "Enums can have methods and decls, just like structs.",
                "`enum(u8) { a, _ }` declares a **non-exhaustive** enum — switching one requires a `_ =>` prong.",
                "`@enumFromInt` with a value that has no tag is a checked panic in safe modes; UB in `ReleaseFast`."
              ]
            }
          ]
        },
        {
          "id": "enum-literals",
          "title": "Enum Literals",
          "summary": ".red means the red tag — of whichever type the context expects.",
          "keywords": [
            "enum literal",
            "decl literal",
            "coercion",
            "dot syntax",
            "switch prongs"
          ],
          "blocks": [
            {
              "kind": "code",
              "lang": "zig",
              "code": "const Color = enum { red, green, blue };\nconst Team = enum { red, blue };\n\nfn paint(c: Color) void { _ = c; }\n\npaint(.red);                       // .red coerces to Color.red\n\nconst t: Team = .red;              // ...and also to Team.red"
            },
            {
              "kind": "list",
              "items": [
                "An enum literal `.red` coerces into **any** enum in the expected type that has a matching tag.",
                "**0.15:** generalized to **decl literals** — `.empty`, `.none` and friends resolve to any reachable decl, e.g. `var list: std.ArrayList(u8) = .empty;`.",
                "**0.16:** decl literals may be used directly in `switch` prongs."
              ]
            }
          ]
        },
        {
          "id": "union-tagged",
          "title": "Tagged Unions",
          "ziglings": "056–057",
          "summary": "One payload at a time, with the compiler tracking which.",
          "keywords": [
            "tagged union",
            "union enum",
            "switch capture",
            "payload",
            "variant",
            "active tag"
          ],
          "blocks": [
            {
              "kind": "code",
              "lang": "zig",
              "code": "const Shape = union(enum) {\n    circle: f64,\n    rect: struct { w: f64, h: f64 },\n    none,\n};\n\nfn area(s: Shape) f64 {\n    return switch (s) {\n        .circle => |r| 3.14159 * r * r,     // capture the payload\n        .rect => |r| r.w * r.h,\n        .none => 0,\n    };\n}\n\nconst s: Shape = .{ .circle = 2.0 };        // init one field\nconst empty = s == .none;                   // tag check: false"
            },
            {
              "kind": "list",
              "items": [
                "`union(enum)` gives every payload a tag; `switch` must handle **all** of them (exhaustive).",
                "Capture payloads in prongs: `.circle => |r|` by value, `.rect => |*r|` for mutation.",
                "Field-less members like `none` carry no data.",
                "`std.meta.activeTag(s)` reads the active tag explicitly; unions are value types."
              ]
            }
          ]
        },
        {
          "id": "union-untagged",
          "title": "Untagged & extern Unions",
          "ziglings": "055",
          "summary": "Raw field aliasing — you own the active-field bookkeeping.",
          "keywords": [
            "untagged union",
            "extern union",
            "aliasing",
            "c abi",
            "undefined behaviour"
          ],
          "blocks": [
            {
              "kind": "code",
              "lang": "zig",
              "code": "const Value = union {\n    int: i32,\n    float: f64,      // same storage — fields alias\n};\n\nvar v: Value = .{ .int = 42 };\nv.float = 3.5;       // overwrites the same bytes"
            },
            {
              "kind": "warn",
              "title": "No tag, no mercy",
              "content": "Nothing tracks which field is active. **Reading a field you did not last write is undefined behaviour** — the compiler assumes the active field and may optimize accordingly. Use a tagged `union(enum)` unless an ABI forces otherwise."
            },
            {
              "kind": "text",
              "content": "`extern union` gives C-compatible overlay layout for FFI (think C's `union`); plain `union` is the in-Zig variant. Both expect **you** to remember the active field."
            }
          ]
        },
        {
          "id": "union-packed",
          "title": "Packed Unions",
          "summary": "Field aliasing at the bit level over a backing integer.",
          "keywords": [
            "packed union",
            "bits",
            "backing integer",
            "equality",
            "registers"
          ],
          "blocks": [
            {
              "kind": "code",
              "lang": "zig",
              "code": "const Reg = packed union(u2) {\n    raw: u2,\n    parts: packed struct(u2) { low: bool, high: bool },\n};\n\nconst r: Reg = .{ .raw = 0b10 };\n\n// 0.16+: packed unions can be compared for equality:\nconst matches = r == .{ .parts = .{ .low = false, .high = true } };"
            },
            {
              "kind": "list",
              "items": [
                "`packed union(uN)` overlays its fields bit-exactly on a backing integer — the classic register-view tool.",
                "**0.16:** packed unions support `==` comparisons and can appear as `switch` prong items."
              ]
            }
          ]
        },
        {
          "id": "enum-vs-union",
          "title": "Choosing: enum vs union vs struct",
          "summary": "A ten-second decision table.",
          "keywords": [
            "enum vs union",
            "variant",
            "decision",
            "struct",
            "tagged",
            "sum type"
          ],
          "blocks": [
            {
              "kind": "table",
              "headers": [
                "If you need...",
                "Reach for",
                "Data"
              ],
              "rows": [
                [
                  "One of N states",
                  "`enum`",
                  "none — just the tag"
                ],
                [
                  "One of N states, each with data",
                  "`union(enum)`",
                  "exactly one active payload"
                ],
                [
                  "Overlapping views of the same bits",
                  "`union` / `packed union` / `extern union`",
                  "you track the active field"
                ],
                [
                  "All fields at once",
                  "`struct`",
                  "everything, always"
                ]
              ]
            },
            {
              "kind": "text",
              "content": "A tagged union is Zig's safe version of C's tagged-struct idiom and C++'s `std::variant`: the tag is compiler-managed, and `switch` refuses to compile until every case is handled."
            }
          ]
        }
      ]
    },
    {
      "id": "optionals",
      "title": "Optionals",
      "icon": "CircleDashed",
      "blurb": "Null without the billion-dollar mistake — `?T` is a value or `null`, and every unwrap is compiler-checked.",
      "entries": [
        {
          "id": "opt-basics",
          "title": "Optional Basics",
          "summary": "`?T` is a value or `null` — test it, default it, or capture it; there is no truthiness.",
          "ziglings": "045–046, 050",
          "keywords": [
            "optional",
            "null",
            "orelse",
            "unwrap",
            "payload capture",
            "nullable",
            "panic"
          ],
          "blocks": [
            {
              "kind": "text",
              "content": "`?T` holds a value of `T` or `null`. Test with `o == null`, default with `orelse`, unwrap-assert with `.?`, or capture the payload with `if (o) |v|`."
            },
            {
              "kind": "code",
              "title": "Create, test, unwrap",
              "lang": "zig",
              "code": "const maybe: ?i32 = null;\nconst n: ?i32 = 42;\n\nif (n) |v| {\n    std.debug.print(\"got {d}\\n\", .{v});  // payload capture\n} else {\n    std.debug.print(\"nothing\\n\", .{});\n}\n\nconst a = maybe orelse 0;        // 0 — fallback when null\nconst b = n.?;                   // 42 — panics if null\nstd.debug.print(\"{d} {d}\\n\", .{ a, b });"
            },
            {
              "kind": "table",
              "headers": [
                "Operation",
                "Value present",
                "`null`"
              ],
              "rows": [
                [
                  "`o orelse d`",
                  "yields `o`",
                  "yields `d`"
                ],
                [
                  "`o.?`",
                  "yields `o`",
                  "**panic**: attempt to use null value"
                ],
                [
                  "`if (o) |v| a else b`",
                  "`a`, with `v` bound",
                  "`b`"
                ],
                [
                  "`while (o) |v| { ... }`",
                  "body per value",
                  "loop ends"
                ]
              ]
            }
          ]
        },
        {
          "id": "opt-while",
          "title": "While-Unwrap & Iterators",
          "summary": "`while (iter.next()) |item|` is the idiomatic iterator loop; `else` fires when the loop ends without `break`.",
          "ziglings": "045–046, 050",
          "keywords": [
            "while",
            "iterator",
            "next",
            "unwrap",
            "else clause",
            "for loop",
            "index",
            "zip"
          ],
          "blocks": [
            {
              "kind": "text",
              "content": "An iterator is any type with `fn next() ?T` — each call yields the next value or `null`. The `while`-unwrap loop consumes it: the body runs per value, the loop ends when `null` arrives."
            },
            {
              "kind": "code",
              "title": "Custom iterator over ?u32",
              "lang": "zig",
              "code": "const Counter = struct {\n    remaining: u32 = 3,\n    fn next(self: *Counter) ?u32 {\n        if (self.remaining == 0) return null;\n        self.remaining -= 1;\n        return self.remaining;        // yields 2, 1, 0\n    }\n};\nvar it = Counter{};\nwhile (it.next()) |item| {            // ?u32 unwrapped per pass\n    std.debug.print(\"{d} \", .{item});\n} else {\n    std.debug.print(\"ended, no break\\n\", .{}); // runs: no break used\n}"
            },
            {
              "kind": "text",
              "content": "The `else` clause — on `while` and `for` alike — runs only when the loop ended **without** `break`; for error unions it takes the last error: `else |e|`. `for (slice, 0..) |item, i|` walks items with their index, and multi-sequence `for (as, bs) |a, b|` zips sequences pairwise."
            }
          ]
        },
        {
          "id": "opt-pointers",
          "title": "Optional Pointers",
          "summary": "`?*T` costs nothing extra — null hides in the pointer's spare bit pattern; `?u32` pays for a tag.",
          "ziglings": "045–046, 050",
          "keywords": [
            "optional pointer",
            "?*t",
            "null optimization",
            "size",
            "tag",
            "struct field",
            "find"
          ],
          "blocks": [
            {
              "kind": "text",
              "content": "`?*T` occupies exactly the same bytes as `*T`: address `0` is never a valid pointer, so it encodes `null` for free. Payloads without a spare bit pattern (`?u32`, `?bool`) need a hidden tag byte. This is why linked-list `next` fields, tree children, and `find`-style APIs returning `?usize` all use optional pointers."
            },
            {
              "kind": "code",
              "lang": "zig",
              "code": "const Entry = struct {\n    id: u32,\n    next: ?*Entry = null,       // linked list: null is free\n};\n\nconst Config = struct {\n    home: ?[]const u8 = null,   // optional slice field\n    fallback: ?*const Config = null,\n};\n\nfn findIndex(hay: []const u8, needle: u8) ?usize {\n    for (hay, 0..) |c, i| {\n        if (c == needle) return i;   // find-style API\n    }\n    return null;\n}"
            },
            {
              "kind": "table",
              "headers": [
                "Type",
                "Size",
                "Why"
              ],
              "rows": [
                [
                  "`?*T`",
                  "== `@sizeOf(*T)`",
                  "`0` is reserved — `null` is free"
                ],
                [
                  "`?*const T`",
                  "== `@sizeOf(*const T)`",
                  "same trick"
                ],
                [
                  "`?u32`",
                  "> `@sizeOf(u32)`",
                  "extra tag, padded to alignment"
                ],
                [
                  "`?bool`",
                  "> `@sizeOf(bool)`",
                  "no spare bit pattern"
                ]
              ]
            }
          ]
        },
        {
          "id": "opt-patterns",
          "title": "Patterns & Gotchas",
          "summary": "orelse chains, nested optionals, and why `catch` is not `orelse`.",
          "ziglings": "045–046, 050",
          "keywords": [
            "nested optional",
            "??t",
            "orelse chain",
            "typeof null",
            "getptr",
            "catch vs orelse",
            "gotchas"
          ],
          "blocks": [
            {
              "kind": "code",
              "lang": "zig",
              "code": "// orelse chain: first non-null wins\nconst home = env_home orelse env_user orelse \"/tmp\";\n\n// capture payload BY POINTER: mutate in place, no copy\nif (map.getPtr(key)) |ptr| ptr.count += 1;\n\n// nested optional ??T: legal but rare — unwrap twice\nconst deep: ??u32 = n;\nif (deep) |inner_opt| {\n    if (inner_opt) |v| std.debug.print(\"{d}\\n\", .{v});\n}"
            },
            {
              "kind": "list",
              "items": [
                "`null` has its own singleton type — `@TypeOf(null)`; optionals are not booleans",
                "`a orelse b orelse c` short-circuits to the first non-null",
                "nested `??T` is allowed but almost always a design smell — prefer `?T` or an error union",
                "`catch` belongs to **error unions**, `orelse` to **optionals** — the two do not interchange"
              ]
            },
            {
              "kind": "warn",
              "content": "`x.?` panics with **attempt to use null value** — and that safety check only exists in Debug and ReleaseSafe. In ReleaseFast it is compiled out: undefined behavior. Prefer `orelse` or `if`-capture unless the invariant is locally provable."
            }
          ]
        }
      ]
    },
    {
      "id": "errors",
      "title": "Error Handling",
      "icon": "TriangleAlert",
      "blurb": "Errors are ordinary values: `E!T` return types, `try`/`catch`, `errdefer` — no exceptions, no unwinding.",
      "entries": [
        {
          "id": "err-sets",
          "title": "Error Sets",
          "summary": "`error{...}` defines a set of error values — checked at compile time, returned, never thrown.",
          "ziglings": "021–025, 033",
          "keywords": [
            "error set",
            "error values",
            "anyerror",
            "coercion",
            "errorset combine",
            "no exceptions"
          ],
          "blocks": [
            {
              "kind": "text",
              "content": "An error set — `error{FileNotFound, OutOfMemory}` — is a distinct set of error **values**, not a class hierarchy. Values coerce upward into any superset for free; `anyerror` accepts every error that exists."
            },
            {
              "kind": "code",
              "lang": "zig",
              "code": "const OpenError = error{ FileNotFound, AccessDenied };\n\n// combine sets: merged superset, coercion is automatic\nconst IoError = OpenError || error{ EndOfStream };\n\n// anyerror: the global set — erases the concrete set\nvar last: anyerror = error.OutOfMemory;\n\nfn open() OpenError!void {\n    return error.FileNotFound;   // must be in the return set\n}"
            },
            {
              "kind": "table",
              "headers": [
                "Form",
                "Meaning"
              ],
              "rows": [
                [
                  "`error{A, B}`",
                  "set literal; values are `error.A`, `error.B`"
                ],
                [
                  "`E1 || E2`",
                  "merged superset (coercion target)"
                ],
                [
                  "`anyerror`",
                  "the global set of all errors"
                ],
                [
                  "`E!T`",
                  "error union: an `E` value or a `T` payload"
                ]
              ]
            },
            {
              "kind": "text",
              "content": "Error sets are **not exceptions**. They are return values: an error must be handled at the call site or propagated explicitly, and nothing ever unwinds."
            }
          ]
        },
        {
          "id": "err-unions",
          "title": "Error Unions & Inferred Sets",
          "summary": "`E!T` is error or value; `!T` lets the compiler infer the set; `try`/`catch` unwrap it.",
          "ziglings": "021–025, 033",
          "keywords": [
            "error union",
            "inferred error set",
            "try",
            "catch",
            "fallback",
            "unreachable",
            "labeled catch",
            "blk"
          ],
          "blocks": [
            {
              "kind": "text",
              "content": "`E!T` is a union: an error value or a payload. `!T` (no set named) means **inferred** — the compiler collects every error the body can return, and inferred sets compose across call chains. Bind one explicitly as `IoError!u32` when you want a stable contract."
            },
            {
              "kind": "code",
              "lang": "zig",
              "code": "fn parse(s: []const u8) !u32 {            // ! = inferred error set\n    if (s.len == 0) return error.Empty;   // raise: return an error\n    return try std.fmt.parseInt(u32, s, 10);\n}\n\nconst v = parse(\"42\") catch 0;            // fallback value\nconst w = parse(\"42\") catch unreachable;  // asserts no error\nconst x = parse(\"nope\") catch |e| blk: {  // labeled catch\n    std.debug.print(\"failed: {s}\\n\", .{@errorName(e)});\n    break :blk 0;\n};"
            },
            {
              "kind": "compare",
              "left": {
                "title": "try",
                "code": "const v = try parse(s);",
                "lang": "zig"
              },
              "right": {
                "title": "catch |e| return e",
                "code": "const v = parse(s) catch |e| return e;",
                "lang": "zig"
              },
              "note": "`try f()` is exactly this sugar: unwrap, or return the error from the enclosing function."
            },
            {
              "kind": "table",
              "headers": [
                "Form",
                "On error",
                "On success"
              ],
              "rows": [
                [
                  "`try f();`",
                  "returns the error from this fn",
                  "yields the value"
                ],
                [
                  "`f() catch 0;`",
                  "yields `0`",
                  "yields the value"
                ],
                [
                  "`f() catch unreachable;`",
                  "panics",
                  "yields the value"
                ],
                [
                  "`f() catch |e| handler;`",
                  "runs handler, `e` in scope",
                  "yields the value"
                ]
              ]
            }
          ]
        },
        {
          "id": "err-handling",
          "title": "Handling Payloads",
          "summary": "Unwrap error unions with `if`/`while` payload capture; switch on the error value itself.",
          "ziglings": "021–025, 033",
          "keywords": [
            "if error",
            "else |e|",
            "while error",
            "retry",
            "switch error",
            "@errorName",
            "payload"
          ],
          "blocks": [
            {
              "kind": "code",
              "lang": "zig",
              "code": "const r: ReadError!u32 = read();\n\nif (r) |v| {                          // success: v is the payload\n    std.debug.print(\"ok {d}\\n\", .{v});\n} else |e| {                          // error: e is the error value\n    switch (e) {                      // switch on the error set\n        error.FileNotFound => create(),\n        else => return e,             // propagate the rest\n    }\n}\n\nwhile (tryConnect()) |conn| {         // body runs on each success\n    serve(conn);\n} else |e| return e;                  // runs on the final error"
            },
            {
              "kind": "text",
              "content": "There is no `switch` on an error union — unwrap with `if (r) |v| ... else |e| ...` first, then `switch` the bare error value. A `switch (e)` over a set must be exhaustive or end in `else`."
            },
            {
              "kind": "list",
              "items": [
                "`@errorName(e)` returns the name as `[]const u8` — the builtin is `@errorName`, there is no `std.error` module",
                "`if (r) |v| { ... } else |e| { ... }` — success and error branches in one statement",
                "`while (attempt()) |v| { ... } else |e| { ... }` — retry loops for free"
              ]
            }
          ]
        },
        {
          "id": "err-defer",
          "title": "errdefer",
          "summary": "`errdefer` runs cleanup only when an error return passes through its scope.",
          "ziglings": "021–025, 033",
          "keywords": [
            "errdefer",
            "cleanup",
            "lifo",
            "error path",
            "resource",
            "payload capture"
          ],
          "blocks": [
            {
              "kind": "text",
              "content": "`errdefer` arms when execution passes it and fires only when an **error return** leaves the scope — never on success. Arming it before the resource's own `try` is pointless: that `try` already bailed out of the function."
            },
            {
              "kind": "code",
              "title": "Resource cleanup ordering (LIFO)",
              "lang": "zig",
              "code": "const conn = try connect(gpa);   // acquire\nerrdefer conn.close();           // undo ONLY if we return an error\n\nconst buf = try gpa.alloc(u8, 64);\nerrdefer gpa.free(buf);          // LIFO: freed before conn closes\n\nerrdefer |e| std.log.warn(\"failed: {s}\", .{@errorName(e)});\n\ntry conn.handshake();            // any failure here or below:\nreturn conn.read();              // log, free buf, close conn, propagate"
            },
            {
              "kind": "table",
              "headers": [
                "Form",
                "Fires"
              ],
              "rows": [
                [
                  "`defer f();`",
                  "every scope exit"
                ],
                [
                  "`errdefer f();`",
                  "scope exit via error return only"
                ],
                [
                  "`errdefer |e| f(e);`",
                  "same, with the error value bound"
                ],
                [
                  "success return after arming",
                  "nothing — the errdefer is skipped"
                ]
              ]
            }
          ]
        },
        {
          "id": "err-panic",
          "title": "Panics & Unreachable",
          "summary": "`@panic` aborts with no unwinding; `unreachable` is a contract the optimizer relies on.",
          "ziglings": "021–025, 033",
          "keywords": [
            "panic",
            "unreachable",
            "assert",
            "abort",
            "releasefast",
            "error return trace",
            "safety"
          ],
          "blocks": [
            {
              "kind": "code",
              "lang": "zig",
              "code": "std.debug.assert(x > 0);           // \"assertion failed\" in safe modes\n\nif (buf.len > max_len) @panic(\"buffer overrun by design\");\n\nfn step(s: Phase) void {\n    switch (s) {\n        .start, .running => advance(s),\n        .done => unreachable,       // invariant: never called when done\n    }\n}"
            },
            {
              "kind": "list",
              "items": [
                "`@panic(\"msg\")` prints message + trace and aborts — **no unwinding: defers do not run**",
                "`unreachable` — `reached unreachable code` panic in Debug/ReleaseSafe; undefined behavior in ReleaseFast",
                "`std.debug.assert(cond)` — panics in Debug/ReleaseSafe, compiled out in ReleaseFast/ReleaseSmall",
                "error return traces print in safe builds — every `try` the error crossed, like a reverse stack trace",
                "gate debug-only code with `builtin.mode == .Debug` (`const builtin = @import(\"builtin\");`)"
              ]
            },
            {
              "kind": "warn",
              "content": "**No exceptions, no catch-alls.** An error is handled where it occurs or returned explicitly — and error unions cannot cross `extern`/`export` boundaries: convert to C-style return codes at the FFI seam."
            }
          ]
        },
        {
          "id": "err-style",
          "title": "Error Style Guide",
          "summary": "Errors carry no payload — keep sets small, add context where it exists, `try` liberally.",
          "ziglings": "021–025, 033",
          "keywords": [
            "error style",
            "error payload",
            "outofmemory",
            "context",
            "logging",
            "main",
            "design"
          ],
          "blocks": [
            {
              "kind": "text",
              "content": "Error values carry no payload or message. The idiomatic response: small per-module sets, context added at the call site that has it, and `errdefer` for cleanup."
            },
            {
              "kind": "list",
              "items": [
                "no payloads, no wrapper types — attach context by logging where you have it, plus `errdefer`",
                "keep error sets small and per-module; `error{OutOfMemory}` is ubiquitous and coerces everywhere",
                "`try` liberally; handle at the call site that actually has context to recover",
                "don't use optionals for failures — `?T` means *absent*, `E!T` means *went wrong*",
                "returning an error from `main` prints the error trace and exits nonzero"
              ]
            },
            {
              "kind": "code",
              "lang": "zig",
              "code": "fn load(gpa: Allocator, path: []const u8) !Config {\n    const file = try openConfig(path);   // low-level error, no context\n    defer file.close();\n\n    return parseConfig(gpa, file) catch |e| {\n        std.log.err(\"config {s}: {s}\", .{ path, @errorName(e) }); // context here\n        return e;                        // propagate the original error\n    };\n}"
            }
          ]
        }
      ]
    },
    {
      "id": "control-flow",
      "title": "Control Flow",
      "icon": "GitBranch",
      "blurb": "Conditions must be real `bool`; `if`, loops, and `switch` are expressions — plus labeled blocks for structured jumps.",
      "entries": [
        {
          "id": "cf-if",
          "title": "if / else if / else",
          "summary": "Conditions must be genuine `bool`; `if` is an expression — that's why there is no ternary.",
          "ziglings": "009–017, 030–031, 062–063, 098, 103–104, 111",
          "keywords": [
            "if",
            "else",
            "bool",
            "truthiness",
            "expression",
            "ternary",
            "payload"
          ],
          "blocks": [
            {
              "kind": "text",
              "content": "No truthiness: `if (ptr)` and `if (n)` are compile errors — compare explicitly. As an expression, every branch must yield the same type, which is exactly what the ternary would do."
            },
            {
              "kind": "code",
              "lang": "zig",
              "code": "const x: i32 = 7;\n\n// statement form: condition must be bool\nif (x > 5) std.debug.print(\"big\\n\", .{});\n\n// expression form: replaces the ternary\nconst parity: []const u8 = if (x % 2 == 0) \"even\" else \"odd\";\n\n// payload sugar (see Optionals / Error Handling)\nif (maybe) |v| use(v);                     // optional\nif (result) |v| use(v) else |e| fail(e);   // error union"
            },
            {
              "kind": "list",
              "items": [
                "braces are optional for a single statement; `zig fmt` keeps whatever you wrote",
                "when used as an expression, all branches must yield one common type",
                "`if (o) |v|` unwraps an optional; `if (r) |v| ... else |e|` unwraps an error union"
              ]
            }
          ]
        },
        {
          "id": "cf-while",
          "title": "while",
          "summary": "`while` with a continue-expression, labeled `break`, and an `else` clause — no `do-while`.",
          "ziglings": "009–017, 030–031, 062–063, 098, 103–104, 111",
          "keywords": [
            "while",
            "continue expression",
            "break",
            "continue",
            "else",
            "label",
            "do while"
          ],
          "blocks": [
            {
              "kind": "text",
              "content": "`while (cond) { }` repeats until the condition is false. The continue-expression `: (i += 1)` runs after every pass — including after `continue` — making it the safest place for increments."
            },
            {
              "kind": "code",
              "lang": "zig",
              "code": "var i: u32 = 0;\nwhile (i < 5) : (i += 1) {    // continue-expr runs after each pass\n    if (i == 3) continue;\n    if (i == 4) break;\n}\n\n// no do-while: use while (true) + break\nwhile (true) {\n    if (ready()) break;\n}\n\n// else: runs when the condition ends the loop WITHOUT a break\nwhile (poll()) |v| {          // optional / error-union unwrap\n    if (v == 0) break;\n} else flush();               // poll() ran dry"
            },
            {
              "kind": "list",
              "items": [
                "`break`/`continue` can target a label: `break :outer`, `continue :outer`",
                "`else` after `while`: fires when the loop ended without `break` (also `else |e|` for error unions)",
                "no `do-while` — write `while (true)` plus a `break`"
              ]
            }
          ]
        },
        {
          "id": "cf-for",
          "title": "for & Ranges",
          "summary": "`for` walks exclusive ranges and slices, zipping sequences with an optional index.",
          "ziglings": "009–017, 030–031, 062–063, 098, 103–104, 111",
          "keywords": [
            "for",
            "range",
            "index",
            "zip",
            "slice",
            "reverse",
            "else",
            "discard"
          ],
          "blocks": [
            {
              "kind": "text",
              "content": "`for (0..n) |i|` iterates the exclusive range `0..n`. Sequences zip by position — `for (xs, ys, 0..) |x, y, i|` — and all lengths must match (runtime check in safe builds)."
            },
            {
              "kind": "code",
              "lang": "zig",
              "code": "for (0..3) |i| {                      // exclusive range: 0, 1, 2\n    std.debug.print(\"{d} \", .{i});\n}\nconst xs = [_]u8{ 10, 20, 30 };\nconst ys = [_]u8{ 1, 2, 3 };\n\nfor (xs, ys, 0..) |x, y, i| {         // zip: item, item, index\n    std.debug.print(\"{d}+{d}@{d} \", .{ x, y, i });\n}                                     // equal lengths, runtime-checked\n\nfor (xs) |x| {\n    if (x == 20) continue;\n    if (x == 30) break;\n} else std.debug.print(\"no break\\n\", .{});"
            },
            {
              "kind": "list",
              "items": [
                "`_` discards a slot: `for (xs, 0..) |_, i|` iterates indices only",
                "no reverse ranges — index backwards by hand, or `std.mem.reverse` a copy first",
                "mutate in place with `for (slice) |*item| item.* += 1`",
                "`else` on `for` runs when the loop finished without `break`"
              ]
            }
          ]
        },
        {
          "id": "cf-switch",
          "title": "switch",
          "summary": "Exhaustive by default; ranges with `...`; prongs capture tagged-union payloads.",
          "ziglings": "009–017, 030–031, 062–063, 098, 103–104, 111",
          "keywords": [
            "switch",
            "prong",
            "range",
            "exhaustive",
            "else",
            "capture",
            "tagged union",
            "string"
          ],
          "blocks": [
            {
              "kind": "text",
              "content": "`switch` is exhaustive by default: cover every value or add `else`. It works as expression or statement, and captures `|v|` bind payloads — especially for tagged unions."
            },
            {
              "kind": "code",
              "lang": "zig",
              "code": "const label = switch (n) {          // expression: prongs yield a value\n    0 => \"zero\",\n    1, 2, 3 => \"small\",             // value list\n    4...8 => \"medium\",              // inclusive range\n    else => \"large\",\n};\n\nfn area(s: Shape) f32 {             // union(enum): capture payloads\n    return switch (s) {\n        .circle => |r| r * r * 3.14159,\n        .rect => |d| d[0] * d[1],\n    };                              // exhaustive: no else needed\n}"
            },
            {
              "kind": "table",
              "headers": [
                "Prong",
                "Matches"
              ],
              "rows": [
                [
                  "`1 => ...`",
                  "a single value"
                ],
                [
                  "`1, 2 => ...`",
                  "a list of values"
                ],
                [
                  "`1...5 => ...`",
                  "an inclusive range"
                ],
                [
                  "`.circle => |r| ...`",
                  "payload capture on `union(enum)`"
                ],
                [
                  "`else => ...`",
                  "everything unmatched"
                ]
              ]
            },
            {
              "kind": "list",
              "items": [
                "prongs are comma-separated — trailing comma when they span lines",
                "switch works on ints, bools, enums, error sets, and `union(enum)`s — not strings or floats (use `if/else` + `std.mem.eql`)",
                "0.16: prongs accept packed struct/union items (`.{ .b = 3 }`) and decl literals as values"
              ]
            }
          ]
        },
        {
          "id": "cf-labels",
          "title": "Labeled Blocks & Loops",
          "summary": "Labels turn nested loops and blocks into structured `goto` — and blocks can yield values.",
          "ziglings": "009–017, 030–031, 062–063, 098, 103–104, 111",
          "keywords": [
            "label",
            "labeled block",
            "break",
            "continue",
            "goto",
            "blk",
            "value"
          ],
          "blocks": [
            {
              "kind": "text",
              "content": "A label names a loop or block so `break :label` / `continue :label` can target it from any nesting depth. Labeled blocks double as multi-step expressions."
            },
            {
              "kind": "code",
              "lang": "zig",
              "code": "outer: for (rows) |row| {\n    for (row) |cell| {\n        if (cell < 0) break :outer;     // exits BOTH loops\n        if (cell == 0) continue :outer; // next outer iteration\n    }\n}\n\nconst idx = blk: {                      // labeled block yields a value\n    for (names, 0..) |name, i| {\n        if (std.mem.eql(u8, name, want)) break :blk i;\n    }\n    break :blk null;                    // not found\n};"
            },
            {
              "kind": "list",
              "items": [
                "labels work on blocks, `for`, and `while` alike",
                "`continue :label` restarts the labeled loop's next iteration",
                "`break :blk value` makes a block an expression — replaces `goto` and early-multi-break acrobatics"
              ]
            }
          ]
        },
        {
          "id": "cf-unreachable",
          "title": "unreachable",
          "summary": "Proves impossibility to the compiler — or costs you UB in optimized builds.",
          "ziglings": "009–017, 030–031, 062–063, 098, 103–104, 111",
          "keywords": [
            "unreachable",
            "undefined behavior",
            "panic",
            "optimization",
            "invariant"
          ],
          "blocks": [
            {
              "kind": "text",
              "content": "`unreachable` asserts a line can never execute. The compiler may optimize based on that promise. Safe modes verify it at runtime: panic **reached unreachable code**. ReleaseFast believes you: undefined behavior."
            },
            {
              "kind": "code",
              "lang": "zig",
              "code": "fn drive(s: State) void {\n    switch (s) {\n        .parked, .driving => move(s),\n        .scrapped => unreachable,   // invariant: can't happen here\n    }\n}"
            },
            {
              "kind": "warn",
              "content": "Never validate input with `unreachable` — hostile data hitting it in ReleaseFast is memory corruption, not a crash. Use `@panic`, `try`, or `else => return error.X` for anything reachable."
            }
          ]
        }
      ]
    },
    {
      "id": "functions",
      "title": "Functions",
      "icon": "SquareFunction",
      "blurb": "Required return types, immutable params, no closures — functions are plain, predictable declarations.",
      "entries": [
        {
          "id": "fn-decl",
          "title": "Declaring Functions",
          "summary": "Return type required, params immutable, no defaults — and `pub` controls visibility.",
          "ziglings": "018–020, 032",
          "keywords": [
            "function",
            "fn",
            "return type",
            "pub",
            "void",
            "doc comment",
            "params"
          ],
          "blocks": [
            {
              "kind": "text",
              "content": "The return type is REQUIRED — use `void` when there is nothing to return. Parameters are immutable bindings, and there are no default or named arguments."
            },
            {
              "kind": "code",
              "lang": "zig",
              "code": "/// Adds two integers.\npub fn add(a: i32, b: i32) i32 {\n    return a + b;\n}\n\npub fn logHello(name: []const u8) void {  // void: no return value\n    std.debug.print(\"hello {s}\\n\", .{name});\n    // params are const: assigning to name is a compile error\n}"
            },
            {
              "kind": "list",
              "items": [
                "`pub` exposes a decl to `@import`ers of this file — private otherwise",
                "parameters are `const`; to change one, copy into a local first",
                "`///` doc comments attach to decls and appear in autodoc",
                "no overloading — use distinct names or comptime dispatch"
              ]
            }
          ]
        },
        {
          "id": "fn-multi-return",
          "title": "Multiple Return Values",
          "summary": "No multi-return sugar — return a struct/tuple, an optional, an error union, or use out-params.",
          "ziglings": "018–020, 032",
          "keywords": [
            "multiple return",
            "tuple",
            "struct return",
            "destructuring",
            "out param",
            "optional",
            "error union"
          ],
          "blocks": [
            {
              "kind": "text",
              "content": "Zig has no multi-value return sugar — pick a shape: `!T` for failure, `?T` for absence, a struct or anonymous tuple for several values, `*T` out-params for mutation."
            },
            {
              "kind": "code",
              "lang": "zig",
              "code": "fn divMod(a: u32, b: u32) struct { q: u32, r: u32 } {\n    return .{ .q = a / b, .r = a % b };\n}\n\nconst .{ .q = q, .r = r } = divMod(17, 5);   // 3 and 2 (0.14+)\nstd.debug.print(\"{d} {d}\\n\", .{ q, r });\n\n// alternatives: ?T (absent), E!T (failure), *T (out-param)\nfn indexOf(s: []const u8, c: u8) ?usize {\n    for (s, 0..) |ch, i| if (ch == c) return i;\n    return null;\n}"
            },
            {
              "kind": "list",
              "items": [
                "tuple return type: `struct { u32, u32 }` — anonymous, destructured by position",
                "destructuring since 0.14: `const .{ .q = q, .r = r } = ...;`",
                "`_` discards a destructured slot you don't need"
              ]
            }
          ]
        },
        {
          "id": "fn-args",
          "title": "Passing Arguments",
          "summary": "Pass-by-value with compiler-optimized lowering — pointers only for mutation or sharing.",
          "ziglings": "018–020, 032",
          "keywords": [
            "by value",
            "pointer",
            "*const",
            "slice coercion",
            "immutability",
            "copy"
          ],
          "blocks": [
            {
              "kind": "text",
              "content": "Arguments pass by value; the compiler picks the cheap lowering, so don't contort signatures over sizes. Reach for pointers when you must mutate the caller's data or share it — and remember params are immutable, so `*T` is the only way to write back."
            },
            {
              "kind": "code",
              "lang": "zig",
              "code": "fn byValue(p: Point) Point {         // copy — fine for small data\n    return .{ .x = p.x * 2, .y = p.y * 2 };\n}\n\nfn scale(p: *Point, f: f32) void {   // mutate the caller's value\n    p.x *= f;\n    p.y *= f;\n}\n\nfn total(items: []const u32) u32 {   // read-only view, any length\n    var sum: u32 = 0;\n    for (items) |v| sum += v;\n    return sum;\n}"
            },
            {
              "kind": "table",
              "headers": [
                "Situation",
                "Signature"
              ],
              "rows": [
                [
                  "read-only, small data",
                  "pass `T` by value"
                ],
                [
                  "read-only, big data",
                  "`[]const T` or `*const T`"
                ],
                [
                  "mutate caller's data",
                  "`*T`"
                ],
                [
                  "accept an array",
                  "param `[]const T` — `[N]T` coerces"
                ],
                [
                  "optional argument",
                  "`?T` explicitly (no default args)"
                ]
              ]
            }
          ]
        },
        {
          "id": "fn-callconv",
          "title": "export, extern & callconv",
          "summary": "`export` publishes C-visible symbols, `extern` declares them, `callconv` pins the ABI.",
          "ziglings": "018–020, 032",
          "keywords": [
            "export",
            "extern",
            "callconv",
            "variadic",
            "c abi",
            "inline",
            "naked"
          ],
          "blocks": [
            {
              "kind": "text",
              "content": "The calling convention decides the ABI. `export`/`extern` bridge Zig to C and the linker; `inline` is a codegen hint that forces call-site expansion."
            },
            {
              "kind": "code",
              "lang": "zig",
              "code": "export fn zig_callback(x: i32) i32 {   // C-visible symbol\n    return x * 2;\n}\n\nextern \"c\" fn printf(fmt: [*:0]const u8, ...) c_int; // C variadic\n\nfn cStyle(x: u32) callconv(.c) u32 {   // explicit C ABI (0.14+)\n    return x + 1;\n}\n\ninline fn square(x: i32) i32 {         // body copied into callsites\n    return x * x;\n}"
            },
            {
              "kind": "list",
              "items": [
                "`export fn f()` emits the symbol for C/linkers; `extern` declares one defined elsewhere",
                "`extern \"c\"` names the library; variadic `...` requires the C calling convention",
                "`callconv(.c)` on a normal fn pins the C ABI (lowercase since 0.14)",
                "`callconv(.naked)` for asm-only stubs — no prologue or epilogue",
                "`inline fn` forces inlining at each call site"
              ]
            }
          ]
        },
        {
          "id": "fn-pointers",
          "title": "Function Pointers",
          "summary": "`*const fn (...) T` types, `&f` spelling, no closures — context goes through a parameter.",
          "ziglings": "018–020, 032",
          "keywords": [
            "function pointer",
            "*const fn",
            "callback",
            "vtable",
            "closure",
            "context",
            "comptime"
          ],
          "blocks": [
            {
              "kind": "text",
              "content": "A function pointer's type is `*const fn (i32, i32) i32`. Take a function's address with `&f` — the safest spelling — then call it like a function. There are no closures: pass captured state through an explicit context parameter."
            },
            {
              "kind": "code",
              "lang": "zig",
              "code": "fn add(a: i32, b: i32) i32 { return a + b; }\nfn sub(a: i32, b: i32) i32 { return a - b; }\n\nconst Op = *const fn (i32, i32) i32;\n\nfn apply(f: Op, a: i32, b: i32) i32 {\n    return f(a, b);\n}\n\nconst f: Op = &add;                    // &fn is the canonical spelling\nstd.debug.print(\"{d} {d}\\n\", .{ apply(f, 3, 4), apply(&sub, 3, 4) });"
            },
            {
              "kind": "list",
              "items": [
                "used for callbacks, interrupt vectors, and hand-rolled vtables (see comptime & Generics for the zero-cost alternative)",
                "capture state C-style: `fn cb(ctx: *anyopaque, x: u32) void` with the context as the first parameter",
                "comptime fn parameters specialize per function: `fn wrap(comptime f: fn () void)`"
              ]
            }
          ]
        },
        {
          "id": "fn-recursion",
          "title": "Recursion",
          "summary": "Runtime recursion just works (until the stack); comptime recursion needs an eval-branch budget.",
          "ziglings": "018–020, 032",
          "keywords": [
            "recursion",
            "stack",
            "comptime",
            "eval branch quota",
            "tail call",
            "infinite"
          ],
          "blocks": [
            {
              "kind": "text",
              "content": "Runtime recursion is plain stack recursion — nothing special, nothing detected. Comptime recursion is bounded by the eval branch quota, raised with `@setEvalBranchQuota`."
            },
            {
              "kind": "code",
              "lang": "zig",
              "code": "fn fib(n: u32) u64 {                  // runtime: plain stack recursion\n    return if (n < 2) n else fib(n - 1) + fib(n - 2);\n}\n\nfn Fib(comptime n: u32) u64 {         // comptime: quota-bounded\n    return if (n < 2) n else Fib(n - 1) + Fib(n - 2);\n}\n\ncomptime {\n    @setEvalBranchQuota(1_000_000);   // default quota is only 1000\n    _ = Fib(25);                      // evaluated during compilation\n}"
            },
            {
              "kind": "list",
              "items": [
                "infinite runtime recursion dies with a stack overflow — not detected, no tail-call guarantee",
                "recursive generic instantiation can explode compile times — memoize or flatten",
                "`@setEvalBranchQuota` raises the comptime step budget for the current evaluation"
              ]
            }
          ]
        }
      ]
    },
    {
      "id": "defer",
      "title": "defer & Cleanup",
      "icon": "Timer",
      "blurb": "Scope-exit cleanup: `defer`, `errdefer`, and the acquire/release discipline that replaces RAII.",
      "entries": [
        {
          "id": "defer-basics",
          "title": "defer",
          "summary": "Runs at the END OF SCOPE — the block, not the function — always, in LIFO order.",
          "ziglings": "027–029",
          "keywords": [
            "defer",
            "scope",
            "lifo",
            "cleanup",
            "raii",
            "mutex",
            "free"
          ],
          "blocks": [
            {
              "kind": "text",
              "content": "`defer` schedules a statement for the end of its **enclosing block** — not the function. Sibling defers run LIFO (last registered, first run) on every exit path: return, error return, `break`."
            },
            {
              "kind": "code",
              "title": "Mutex + heap example",
              "lang": "zig",
              "code": "fn demo(gpa: Allocator) !void {\n    const data = try gpa.alloc(u8, 128);\n    defer gpa.free(data);           // paired right at acquisition\n\n    lock.lock();\n    defer lock.unlock();            // LIFO: runs BEFORE free\n\n    // ... work — early returns, errors, breaks: all safe\n}"
            },
            {
              "kind": "warn",
              "content": "Defers do **not** run on panic or abort — the process dies on the spot. One more reason panics are for bugs and errors are for expectations."
            }
          ]
        },
        {
          "id": "defer-errdefer",
          "title": "errdefer",
          "summary": "Same LIFO machinery, but only on the failure path — combine for commit/rollback.",
          "ziglings": "027–029",
          "keywords": [
            "errdefer",
            "defer",
            "commit",
            "rollback",
            "transaction",
            "error path"
          ],
          "blocks": [
            {
              "kind": "text",
              "content": "`errdefer` fires only when an error return passes through its scope — never on success. The classic combo: `errdefer rollback` right after acquiring, `commit` as the last step. Two lines make a transaction."
            },
            {
              "kind": "code",
              "lang": "zig",
              "code": "const conn = try connect(gpa);\nerrdefer conn.close();          // only on error return\ntry conn.handshake();           // bail? -> closed for you\n\ntry tx.begin();\nerrdefer tx.rollback();         // failure path\ntry tx.write(records);\ntx.commit();                    // success path — keep it last"
            },
            {
              "kind": "table",
              "headers": [
                "Exit path",
                "`defer`",
                "`errdefer`"
              ],
              "rows": [
                [
                  "success `return`",
                  "runs",
                  "skipped"
                ],
                [
                  "error `return` / failed `try`",
                  "runs",
                  "runs"
                ],
                [
                  "leave the scope via `break`",
                  "runs",
                  "skipped"
                ],
                [
                  "panic / abort",
                  "skipped",
                  "skipped"
                ]
              ]
            }
          ]
        },
        {
          "id": "defer-scopes",
          "title": "Scope Rules & Gotchas",
          "summary": "defer is block-scoped: in a loop body it fires every iteration — or holds resources the whole loop.",
          "ziglings": "027–029",
          "keywords": [
            "scope rules",
            "loop",
            "iteration",
            "capture",
            "block",
            "gotcha"
          ],
          "blocks": [
            {
              "kind": "text",
              "content": "defer binds to the nearest enclosing block. In a loop body that is one iteration; inside an `if` block, that block; at function top, the function."
            },
            {
              "kind": "code",
              "lang": "zig",
              "code": "fn scan(gpa: Allocator, paths: []const []const u8) !void {\n    for (paths) |path| {\n        const f = try open(path);            // acquire per iteration\n        defer f.close();                     // released per iteration\n\n        const buf = try gpa.alloc(u8, 1024); // also per iteration\n        defer gpa.free(buf);                 // LIFO in this body:\n        try process(f, buf);                 // free buf, then close f\n    }\n}"
            },
            {
              "kind": "list",
              "items": [
                "defer reads variables at RUN time, not at registration — `defer log(i)` sees `i` as it is when the scope exits",
                "want the value as it was at defer-time? snapshot first: `const j = i; defer use(j);`",
                "leaving the scope via `break` still runs defers — only panic/abort skips them"
              ]
            },
            {
              "kind": "warn",
              "content": "A defer **outside** a loop body holds the resource for the whole loop. If each iteration should own the resource, move the acquire/defer pair into the body."
            }
          ]
        },
        {
          "id": "defer-patterns",
          "title": "Common Cleanup Table",
          "summary": "acquire → defer release, immediately, before any `try` in between.",
          "ziglings": "027–029",
          "keywords": [
            "cleanup table",
            "acquire",
            "release",
            "lock",
            "allocator",
            "file",
            "refcount",
            "transaction"
          ],
          "blocks": [
            {
              "kind": "text",
              "content": "The discipline that makes defer work: every acquire is IMMEDIATELY followed by its release deferral — before any `try`, `return`, or second acquire can slip between them."
            },
            {
              "kind": "table",
              "headers": [
                "Acquire",
                "Release",
                "Notes"
              ],
              "rows": [
                [
                  "`gpa.alloc(u8, n)`",
                  "`gpa.free(mem)`",
                  "same allocator, same scope"
                ],
                [
                  "open a file",
                  "`f.close()`",
                  "whatever opened it closes it"
                ],
                [
                  "`lock.lock()`",
                  "`lock.unlock()`",
                  "`std.Thread.Mutex`"
                ],
                [
                  "acquire a refcount",
                  "release it",
                  "balance every +1 with a -1"
                ],
                [
                  "`tx.begin()`",
                  "`tx.commit()`",
                  "`errdefer tx.rollback()`"
                ],
                [
                  "suspend",
                  "resume",
                  "keep the pair visually adjacent"
                ]
              ]
            },
            {
              "kind": "code",
              "lang": "zig",
              "code": "const mem = try gpa.alloc(u8, size);\ndefer gpa.free(mem);             // <- no try between these two lines\n\nconst out = try dest.begin();    // next resource\ndefer out.finish();              // paired before any risk"
            }
          ]
        }
      ]
    },
    {
      "id": "comptime-generics",
      "title": "comptime & Generics",
      "icon": "Sparkles",
      "blurb": "Run arbitrary code at compile time — generics are just functions on `type`, with no macro preprocessor in sight.",
      "entries": [
        {
          "id": "ct-fundamentals",
          "title": "comptime Fundamentals",
          "summary": "`comptime` marks code the compiler executes at build time — params, vars, and blocks.",
          "ziglings": "064–075, 084",
          "keywords": [
            "comptime",
            "compile time",
            "comptime_int",
            "macro",
            "code generation",
            "inline while"
          ],
          "blocks": [
            {
              "kind": "text",
              "content": "`comptime` marks code the compiler runs at build time: parameters, variables, whole blocks. All generics, `type` values, and default values are comptime — this one mechanism replaces C macros, templates, and pre-build codegen."
            },
            {
              "kind": "code",
              "lang": "zig",
              "code": "fn repeat(comptime n: u32, msg: []const u8) void {\n    comptime var i: u32 = 0;              // comptime variable\n    inline while (i < n) : (i += 1) {     // unrolled at compile time\n        std.debug.print(\"{s} \", .{msg});\n    }\n}\n\nconst big = 1_000_000_000_000;   // comptime_int: unbounded, exact\nconst small: u8 = 200;           // coerced to u8 at compile time"
            },
            {
              "kind": "list",
              "items": [
                "`comptime_int` / `comptime_float`: unbounded, exact literal types — they coerce into concrete types on use",
                "`if (comptime cond)` evaluates now and prunes the untaken branch from the binary",
                "a `comptime` parameter creates a distinct instantiation per value"
              ]
            }
          ]
        },
        {
          "id": "ct-generics-fn",
          "title": "Generic Functions",
          "summary": "`type` is a comptime value — take it as a parameter and the fn gets monomorphized per call.",
          "ziglings": "064–075, 084",
          "keywords": [
            "generic",
            "type parameter",
            "anytype",
            "monomorphization",
            "duck typing",
            "@hasDecl"
          ],
          "blocks": [
            {
              "kind": "text",
              "content": "A `type` parameter makes the function generic — `T` is just a comptime value. Every distinct `T` produces a fresh monomorphized copy of the body; there is no runtime polymorphism involved."
            },
            {
              "kind": "code",
              "lang": "zig",
              "code": "fn max(comptime T: type, a: T, b: T) T {\n    return if (a > b) a else b;\n}\n\nconst a = max(i32, 3, 9);        // explicit type argument\nconst b = max(u8, 1, 2);         // each T = a fresh instantiation\n\nfn sum(items: anytype) i64 {     // anytype: inferred param type\n    var total: i64 = 0;\n    for (items) |x| total += x;\n    return total;\n}"
            },
            {
              "kind": "list",
              "items": [
                "`anytype`: infer the parameter type from the call site",
                "`@hasDecl(T, \"name\")` / `@hasField(T, \"name\")` — duck-type checks on capabilities",
                "constraints are implicit — misuse fails where used; add `@compileError` for clear messages"
              ]
            }
          ]
        },
        {
          "id": "ct-generics-type",
          "title": "Generic Types (type-returning fns)",
          "summary": "A function returning `type` is a generic type; `@This()` names the struct from inside.",
          "ziglings": "064–075, 084",
          "keywords": [
            "generic type",
            "type function",
            "@this",
            "struct",
            "instantiation",
            "container"
          ],
          "blocks": [
            {
              "kind": "text",
              "content": "Generic types are functions that return `type`. The struct declared inside is a fresh type for every `T`; identical arguments always yield the identical type."
            },
            {
              "kind": "code",
              "title": "Canonical generic container",
              "lang": "zig",
              "code": "fn List(comptime T: type) type {\n    return struct {\n        items: []T,\n        len: usize = 0,\n\n        const Self = @This();    // name this struct from inside\n\n        fn push(self: *Self, item: T) void {\n            self.items[self.len] = item;\n            self.len += 1;\n        }\n    };\n}\n\nconst IntList = List(i32);       // fresh type, monomorphized"
            },
            {
              "kind": "list",
              "items": [
                "`@This()` — the innermost type's own name, for `Self`-style aliases and methods",
                "methods close over the generic parameter — no extra plumbing",
                "instantiate like any decl: `const IntList = List(i32);`"
              ]
            }
          ]
        },
        {
          "id": "ct-inline",
          "title": "inline for / inline while",
          "summary": "Unroll loops at comptime — one stamped-out copy per iteration.",
          "ziglings": "064–075, 084",
          "keywords": [
            "inline for",
            "inline while",
            "unroll",
            "fields",
            "@field",
            "comptime string"
          ],
          "blocks": [
            {
              "kind": "text",
              "content": "`inline for` / `inline while` force unrolling: the body is stamped out once per iteration at compile time. Required whenever each iteration must generate distinct code from comptime values."
            },
            {
              "kind": "code",
              "lang": "zig",
              "code": "const Point = struct { x: u32 = 0, y: u32 = 0 };\n\nfn zeroAll(p: *Point) void {\n    inline for (@typeInfo(Point).@\"struct\".fields) |field| {\n        @field(p, field.name) = 0;    // x, then y — unrolled\n    }\n}"
            },
            {
              "kind": "list",
              "items": [
                "`field.name` is a comptime string, and `@field(obj, name)` needs a comptime-known name — a runtime loop can't supply either",
                "plain loops stay runtime loops when nothing per-iteration is comptime — inline loops bloat binaries",
                "`inline while` unrolls the same way"
              ]
            }
          ]
        },
        {
          "id": "ct-type-builtins",
          "title": "Type-Creating Builtins (0.16)",
          "summary": "`@Type` is gone in 0.16 — construct types with focused builtins instead.",
          "since": "0.16",
          "ziglings": "064–075, 084",
          "keywords": [
            "@type",
            "removed",
            "@int",
            "@float",
            "@array",
            "@tuple",
            "@optional",
            "0.16"
          ],
          "blocks": [
            {
              "kind": "text",
              "content": "`@Type` was removed in 0.16 — constructing types now goes through focused builtins, one per kind. Reach for them only when the type cannot be spelled directly."
            },
            {
              "kind": "table",
              "headers": [
                "Builtin",
                "Builds",
                "Sketch"
              ],
              "rows": [
                [
                  "`@Int`",
                  "integer type",
                  "`@Int(signedness, bits)`"
                ],
                [
                  "`@Float`",
                  "float type",
                  "`@Float(bits)`"
                ],
                [
                  "`@Pointer`",
                  "pointer type",
                  "size / child / alignment args"
                ],
                [
                  "`@Array`",
                  "array type",
                  "`@Array(len, child)`"
                ],
                [
                  "`@Vector`",
                  "SIMD vector",
                  "`@Vector(len, child)`"
                ],
                [
                  "`@Tuple`",
                  "tuple type",
                  "`@Tuple(&.{ u8, u16 })`"
                ],
                [
                  "`@Struct`",
                  "struct type",
                  "fields arg — see docs"
                ],
                [
                  "`@Union`",
                  "union type",
                  "see docs"
                ],
                [
                  "`@Enum`",
                  "enum type",
                  "see docs"
                ],
                [
                  "`@Opaque`",
                  "opaque type",
                  "see docs"
                ],
                [
                  "`@Optional`",
                  "optional type",
                  "`@Optional(u32)` → `?u32`"
                ],
                [
                  "`@ErrorUnion`",
                  "error union",
                  "set + payload args"
                ],
                [
                  "`@ErrorSet`",
                  "error set",
                  "names arg — see docs"
                ],
                [
                  "`@Fn`",
                  "function type",
                  "see docs"
                ],
                [
                  "`@EnumLiteral`",
                  "enum literal type",
                  "the type of `.tag`"
                ]
              ]
            },
            {
              "kind": "warn",
              "content": "These shipped with `@Type`'s removal in **0.16** and signatures are still settling — check the std docs for your exact version. For most code, spelling types directly (`?u32`, `[4]u8`, `*T`) beats constructing them."
            }
          ]
        },
        {
          "id": "ct-toolkit",
          "title": "Metaprogramming Toolkit",
          "summary": "Reflect, generate, and fail fast — the everyday comptime toolbox.",
          "ziglings": "064–075, 084",
          "keywords": [
            "@typeinfo",
            "@field",
            "@hasfield",
            "@compileError",
            "@setEvalBranchQuota",
            "@embedFile",
            "std.meta.fields",
            "usingnamespace"
          ],
          "blocks": [
            {
              "kind": "text",
              "content": "Reflection and generation in one toolbox — everything here is evaluated during the build, never at runtime."
            },
            {
              "kind": "table",
              "headers": [
                "Tool",
                "What it does"
              ],
              "rows": [
                [
                  "`@typeInfo(T)`",
                  "reflect — tags like `.int`, `.@\"struct\"`, `.pointer`, `.error_union`"
                ],
                [
                  "`@field(obj, \"name\")`",
                  "access a field or decl by comptime name"
                ],
                [
                  "`@hasField(T, \"name\")`",
                  "does the struct have this field?"
                ],
                [
                  "`@typeName(T)`",
                  "human-readable type name"
                ],
                [
                  "`@sizeOf(T)` / `@offsetOf(T, \"f\")`",
                  "layout facts"
                ],
                [
                  "`@call(.auto, f, .{args})`",
                  "call with a comptime-built argument tuple"
                ],
                [
                  "`@compileError(\"msg\")`",
                  "fail compilation with your message"
                ],
                [
                  "`@compileLog(...)`",
                  "print values during compilation — remove before shipping"
                ],
                [
                  "`@setEvalBranchQuota(n)`",
                  "raise the comptime step budget (default 1000)"
                ],
                [
                  "`@embedFile(path)`",
                  "file contents as `*const [n:0]u8` at comptime"
                ],
                [
                  "`std.meta.fields(T)`",
                  "field list as a comptime slice"
                ],
                [
                  "`usingnamespace`",
                  "**removed since 0.15** — no replacement; declare and import explicitly"
                ]
              ]
            },
            {
              "kind": "code",
              "title": "Comptime type validation",
              "lang": "zig",
              "code": "fn checkPairs(comptime T: type) void {\n    inline for (@typeInfo(T).@\"struct\".fields) |f| {\n        if (@sizeOf(f.type) == 0) {\n            @compileError(\"zero-sized field: \" ++ f.name);\n        }\n    }\n}\n\nconst Bad = struct { ok: u32, ghost: u0 };\ncomptime checkPairs(Bad);   // compile error: zero-sized field: ghost"
            }
          ]
        },
        {
          "id": "ct-interfaces",
          "title": "Duck-Typed Interfaces (no vtables)",
          "summary": "Interfaces are comptime checks — or explicit fn-pointer structs when the type is runtime-chosen.",
          "ziglings": "084",
          "keywords": [
            "interface",
            "duck typing",
            "vtable",
            "@hasDecl",
            "anyopaque",
            "std.io.writer",
            "runtime dispatch"
          ],
          "blocks": [
            {
              "kind": "code",
              "title": "Compile-time interface check (ziglings 084 pattern)",
              "lang": "zig",
              "code": "fn describe(comptime T: type, thing: T) void {\n    comptime {\n        if (!@hasDecl(T, \"describe\"))\n            @compileError(@typeName(T) ++ \" needs a describe() decl\");\n    }\n    thing.describe();          // duck typing, resolved at compile time\n}\n\nconst Cat = struct {\n    fn describe(self: Cat) void {\n        std.debug.print(\"meow\\n\", .{});\n    }\n};"
            },
            {
              "kind": "text",
              "content": "When the concrete type is chosen at **runtime**, comptime duck typing is out. Build a vtable by hand — a struct of function pointers plus a context pointer. That is exactly how `std.Io.Writer` / `std.Io.Reader` dispatch since **0.15**."
            },
            {
              "kind": "code",
              "title": "Hand-rolled vtable",
              "lang": "zig",
              "code": "const Speaker = struct {\n    ctx: *anyopaque,\n    speakFn: *const fn (*anyopaque) void,\n\n    fn speak(self: Speaker) void {\n        self.speakFn(self.ctx);      // runtime dispatch\n    }\n};"
            },
            {
              "kind": "list",
              "items": [
                "comptime interfaces: zero cost, monomorphized — but the type must be known at compile time",
                "vtable interfaces: one copy of code, runtime dispatch — works with runtime-chosen types",
                "`*anyopaque` context + `@ptrCast` back is the standard state-capture idiom (there are no closures)"
              ]
            }
          ]
        }
      ]
    },
    {
      "id": "memory-allocators",
      "title": "Memory & Allocators",
      "icon": "Database",
      "blurb": "Explicit allocation: no GC, no hidden malloc.",
      "entries": [
        {
          "id": "mem-allocator-iface",
          "title": "The Allocator Interface",
          "summary": "Every allocation goes through a passed-in `std.mem.Allocator` — nothing allocates behind your back.",
          "ziglings": "099",
          "keywords": [
            "allocator",
            "std.mem.allocator",
            "create",
            "destroy",
            "alloc",
            "free",
            "dupe",
            "realloc",
            "remap"
          ],
          "blocks": [
            {
              "kind": "text",
              "content": "Zig has **no hidden allocations**. Anything that needs memory takes a `std.mem.Allocator` — by convention it is the **first parameter**. The allocator is a small vtable interface, so the caller picks the strategy (arena, debug, fixed buffer, C, ...)."
            },
            {
              "kind": "table",
              "headers": [
                "allocate",
                "release",
                "returns"
              ],
              "rows": [
                [
                  "`create(T)`",
                  "`destroy(ptr)`",
                  "`!*T`"
                ],
                [
                  "`alloc(T, n)`",
                  "`free(slice)`",
                  "`![]T`"
                ],
                [
                  "`allocSentinel(T, n, 0)`",
                  "`free(slice)`",
                  "`![:n]T`"
                ],
                [
                  "`dupe(T, slice)` / `dupeZ(T, slice)`",
                  "`free`",
                  "`![]T` / `![:0]T`"
                ],
                [
                  "`realloc(slice, n)`",
                  "`free`",
                  "`![]T`"
                ],
                [
                  "`remap(slice, n)`",
                  "`free`",
                  "`?[]T` — may **move**"
                ],
                [
                  "`resize(slice, n)`",
                  "—",
                  "`bool` — in-place only"
                ]
              ]
            },
            {
              "kind": "text",
              "content": "All allocating operations return errors (`!`) — out of memory is an ordinary error value, never a panic. The caller owns the result and must free it (or hand the job to an arena)."
            },
            {
              "kind": "code",
              "title": "Allocator as first parameter",
              "code": "fn readName(gpa: std.mem.Allocator) ![]u8 {\n    const buf = try gpa.alloc(u8, 32);\n    defer gpa.free(buf);\n    buf[0] = 'z';\n    return try gpa.dupe(u8, buf[0..1]); // caller frees this copy\n}"
            }
          ]
        },
        {
          "id": "mem-arena",
          "title": "ArenaAllocator",
          "summary": "Allocate freely, free everything at once — the workhorse for parsers, CLIs and request handling.",
          "keywords": [
            "arena",
            "arenaallocator",
            "reset",
            "retain_capacity",
            "scratch",
            "request scope",
            "per frame"
          ],
          "blocks": [
            {
              "kind": "text",
              "content": "An arena hands out allocations from big chunks it grabs from a child allocator. Individual `free` is not needed (or possible for single items) — **everything is released at once** by `deinit()`."
            },
            {
              "kind": "code",
              "title": "Scratch memory for one operation",
              "code": "fn handle(gpa: std.mem.Allocator) ![]u8 {\n    var arena = std.heap.ArenaAllocator.init(gpa);\n    defer arena.deinit(); // frees everything allocated below\n    const a = arena.allocator();\n\n    const tmp = try a.alloc(u8, 64);\n    const msg = try std.fmt.allocPrint(a, \"parsed {d} tokens\", .{3});\n    // ... use tmp and msg freely - no per-slice frees ...\n    return try gpa.dupe(u8, msg); // only the result escapes\n}"
            },
            {
              "kind": "list",
              "items": [
                "`.init(child)` — wrap any allocator; `.allocator()` — get the interface; `.deinit()` — free all",
                "`reset(.retain_capacity)` — keep the memory, drop the allocations: reuse the same arena between phases (per frame, per request, per iteration)",
                "Ideal for **CLIs, parsers, request handling**: one arena per unit of work",
                "Since **0.16** the arena is thread-safe and lock-free"
              ]
            }
          ]
        },
        {
          "id": "mem-debug-alloc",
          "title": "DebugAllocator (ex-GPA)",
          "summary": "`GeneralPurposeAllocator` was renamed `std.heap.DebugAllocator` in 0.16 — leak and double-free detection for development builds.",
          "since": "0.16",
          "keywords": [
            "gpa",
            "generalpurposeallocator",
            "debugallocator",
            "smp_allocator",
            "page_allocator",
            "c_allocator",
            "fixedbufferallocator",
            "leak detection"
          ],
          "blocks": [
            {
              "kind": "code",
              "title": "The standard development setup",
              "code": "var gpa: std.heap.DebugAllocator(.{}) = .init(.{});\ndefer std.debug.assert(gpa.deinit() == .ok); // .ok == no leaks\nconst allocator = gpa.allocator();\n\nconst n = try allocator.create(u32);\nn.* = 5;\ndefer allocator.destroy(n);"
            },
            {
              "kind": "text",
              "content": "`GeneralPurposeAllocator` was renamed `std.heap.DebugAllocator` since **0.16**. In Debug builds it tracks allocations and reports **leaks and double-frees** with stack traces; `gpa.deinit()` returns `.ok` when nothing leaked."
            },
            {
              "kind": "table",
              "headers": [
                "allocator",
                "reach for it when"
              ],
              "rows": [
                [
                  "`std.heap.DebugAllocator(.{})`",
                  "default in development: leak / double-free checks"
                ],
                [
                  "`std.heap.smp_allocator`",
                  "ReleaseFast multi-threaded programs (0.16)"
                ],
                [
                  "`std.heap.ArenaAllocator`",
                  "many small allocations, freed all at once"
                ],
                [
                  "`std.heap.c_allocator`",
                  "your program links libc anyway"
                ],
                [
                  "`std.heap.page_allocator`",
                  "raw OS pages, no bookkeeping, coarse"
                ],
                [
                  "`std.heap.FixedBufferAllocator`",
                  "no-heap targets, embedded, bounded scratch"
                ]
              ]
            }
          ]
        },
        {
          "id": "mem-lifetime",
          "title": "Lifetime Rules",
          "summary": "No GC: freed memory stays freed. Ownership is a convention you enforce with discipline and tooling.",
          "keywords": [
            "lifetime",
            "use after free",
            "double free",
            "dangling",
            "ownership",
            "remap",
            "shrink"
          ],
          "blocks": [
            {
              "kind": "list",
              "items": [
                "Freed memory stays freed — every `alloc` needs exactly one `free` (or an arena `deinit`)",
                "Use-after-free and double-free are **UB**; `DebugAllocator` turns them into clear panics in Debug",
                "Slices from an allocator are only valid while the allocator lives — keep it alive as long as the data",
                "`resize` returns `bool` and only succeeds in place; `remap` may **move** — always use the returned slice, old pointers can dangle after a grow/shrink",
                "Never free stack memory through an allocator; never return a slice that points into a local buffer"
              ]
            },
            {
              "kind": "warn",
              "title": "UB in one line",
              "content": "Using a slice after `free` (or after arena `deinit` / `reset`) is undefined behavior. In Debug builds `std.heap.DebugAllocator` catches it; in ReleaseFast it is silent corruption."
            },
            {
              "kind": "code",
              "title": "remap may move your memory",
              "code": "const p = try gpa.alloc(u8, 8);\nconst q = gpa.remap(p, 16) orelse return error.OutOfMemory;\n// p may be invalid now - use q from here on\n@memset(q, 0);\ngpa.free(q);"
            }
          ]
        },
        {
          "id": "mem-patterns",
          "title": "Patterns & Pitfalls",
          "summary": "Arena per request, allocator as first parameter, and what to do about `deinit()` results.",
          "keywords": [
            "patterns",
            "arena per request",
            "pass allocator down",
            "unmanaged",
            "discard",
            "testing.allocator",
            "deinit"
          ],
          "blocks": [
            {
              "kind": "list",
              "items": [
                "**Arena per request**: one arena per unit of work, `reset(.retain_capacity)` between iterations",
                "**Pass the allocator down**: functions take `gpa: std.mem.Allocator` as first parameter — no global heap",
                "**Store an allocator in a struct** only if the object owns its allocations; prefer taking it per call (unmanaged containers do exactly this since 0.15)",
                "`_ = gpa.deinit();` — discard the leak-report result when you intentionally leak at process exit",
                "Test for leaks with `std.testing.allocator` — it fails the test on any unfreed memory (see **Testing**)"
              ]
            },
            {
              "kind": "code",
              "title": "Arena inside, allocator passed down",
              "code": "const Parser = struct {\n    gpa: std.mem.Allocator,\n\n    fn parse(self: *Parser) !void {\n        var arena = std.heap.ArenaAllocator.init(self.gpa);\n        defer arena.deinit(); // scratch memory freed in one shot\n        const tmp = try arena.allocator().alloc(u8, 64);\n        _ = tmp;\n        // long-lived results allocate from self.gpa instead\n    }\n};"
            }
          ]
        }
      ]
    },
    {
      "id": "stdlib",
      "title": "Standard Library",
      "icon": "Package",
      "blurb": "Formatting, containers, hashing, files, processes — and the 0.15/0.16 API shake-up.",
      "entries": [
        {
          "id": "std-printing",
          "title": "Printing & Writers (0.16)",
          "summary": "`std.debug.print` for quick stderr output; stdout goes through the `std.Io` writer interface.",
          "since": "0.16",
          "ziglings": "002",
          "keywords": [
            "print",
            "stdout",
            "stderr",
            "writer",
            "format",
            "specifier",
            "flush",
            "writestreamingall"
          ],
          "blocks": [
            {
              "kind": "text",
              "content": "`std.debug.print(\"...\", .{args})` writes formatted text to **stderr**, cannot fail, and works in every build mode and target. For **stdout** you go through the `std.Io` interface."
            },
            {
              "kind": "code",
              "title": "stderr vs stdout",
              "code": "// stderr, formatted - the everyday printf:\nstd.debug.print(\"x={d} name={s}\\n\", .{ x, name });\n\n// stdout, raw bytes (0.16, from \"Juicy main\"):\ntry std.Io.File.stdout().writeStreamingAll(init.io, \"hello\\n\");\n\n// buffered formatted stdout (0.15+ shape):\nvar buffer: [1024]u8 = undefined;\nvar w = std.Io.File.stdout().writer(&buffer);\ntry w.interface.print(\"count={d}\\n\", .{ n });\ntry w.interface.flush();",
              "note": "The 0.15/0.16 writer signatures are still settling — see the std docs for your exact version."
            },
            {
              "kind": "table",
              "headers": [
                "specifier",
                "meaning"
              ],
              "rows": [
                [
                  "`{s}`",
                  "string / bytes"
                ],
                [
                  "`{d}`",
                  "int or float, decimal"
                ],
                [
                  "`{c}`",
                  "character"
                ],
                [
                  "`{x}` / `{X}`",
                  "hexadecimal, lowercase / uppercase"
                ],
                [
                  "`{b}`",
                  "binary"
                ],
                [
                  "`{o}`",
                  "octal"
                ],
                [
                  "`{e}`",
                  "scientific notation"
                ],
                [
                  "`{?}`",
                  "optional of a formattable value"
                ],
                [
                  "`{any}`",
                  "anything (debug formatting)"
                ],
                [
                  "`{f}`",
                  "call the type's own `.format` method (since 0.15)"
                ],
                [
                  "`{{` / `}}`",
                  "literal braces"
                ],
                [
                  "`{d:0>4}`",
                  "width 4, fill `0`, align right"
                ],
                [
                  "`{s:^10}`",
                  "width 10, centered"
                ]
              ]
            },
            {
              "kind": "list",
              "items": [
                "Arguments are passed as an anonymous tuple: `.{ x, name }`",
                "`std.debug.print` returns `void` (best-effort, no error handling needed)",
                "Writer-based printing returns errors — `try` it and `flush()` at the end"
              ]
            }
          ]
        },
        {
          "id": "std-string-building",
          "title": "Building Strings",
          "summary": "`allocPrint` for one-shots, `std.mem.concat` for joins, `Writer.Allocating` for incremental building.",
          "ziglings": "106",
          "keywords": [
            "allocprint",
            "concat",
            "writer.allocating",
            "toownedslice",
            "string building",
            "fmt",
            "owned slice"
          ],
          "blocks": [
            {
              "kind": "code",
              "title": "One-shot and concat",
              "code": "const s = try std.fmt.allocPrint(gpa, \"a={d} b={s}\", .{ a, b });\ndefer gpa.free(s);\n\nconst joined = try std.mem.concat(gpa, u8, &.{ \"foo\", \"bar\" });\ndefer gpa.free(joined);"
            },
            {
              "kind": "code",
              "title": "Incremental: Writer.Allocating (0.16)",
              "code": "var aw: std.Io.Writer.Allocating = .init(gpa);\ndefer aw.deinit();\ntry aw.writer.print(\"n={d}\\n\", .{ n }); // .writer is a FIELD, not a method\nconst owned = try aw.toOwnedSlice(); // take over the buffer",
              "note": "`aw.written()` gives a **borrowed** view of what was written so far; `toOwnedSlice()` transfers ownership instead."
            },
            {
              "kind": "text",
              "content": "Rule of thumb: one formatted string → `allocPrint`; appending in a loop → `Writer.Allocating` or an `ArrayList(u8)` (see **ArrayList**)."
            }
          ]
        },
        {
          "id": "std-arraylist",
          "title": "ArrayList",
          "summary": "Growable array: unmanaged since 0.15 (allocator per call), `.empty` initialization since 0.16.",
          "since": "0.15",
          "ziglings": "102",
          "keywords": [
            "arraylist",
            "append",
            "pop",
            "orderedremove",
            "swapremove",
            "items",
            "ensuretotalcapacity",
            "toownedslice",
            "unmanaged"
          ],
          "blocks": [
            {
              "kind": "text",
              "content": "`std.ArrayList(T)` is a growable array. Since **0.15** only the **unmanaged** form exists — the allocator is passed to each method. Since **0.16** you initialize with `.empty`."
            },
            {
              "kind": "code",
              "title": "0.16 style",
              "code": "var list: std.ArrayList(u32) = .empty;\ndefer list.deinit(gpa);\ntry list.append(gpa, 1);\ntry list.appendSlice(gpa, &.{ 2, 3 });\nlist.items[0] = 9;\nconst owned = try list.toOwnedSlice(gpa);"
            },
            {
              "kind": "table",
              "headers": [
                "method",
                "notes"
              ],
              "rows": [
                [
                  "`append(gpa, v)` / `appendSlice(gpa, s)`",
                  "add at the end, `!void`"
                ],
                [
                  "`insert(gpa, i, v)` / `insertSlice(gpa, i, s)`",
                  "shift right, `!void`"
                ],
                [
                  "`pop()`",
                  "returns `?T` since 0.15 — `null` when empty"
                ],
                [
                  "`orderedRemove(i)`",
                  "returns `T`, keeps order, O(n)"
                ],
                [
                  "`swapRemove(i)`",
                  "returns `T`, swaps in last element, O(1)"
                ],
                [
                  "`items` / `items.len`",
                  "the underlying slice / element count"
                ],
                [
                  "`clearRetainingCapacity()`",
                  "len = 0, memory kept for reuse"
                ],
                [
                  "`resize(gpa, n)` / `ensureTotalCapacity(gpa, n)`",
                  "grow (`!void`)"
                ],
                [
                  "`toOwnedSlice(gpa)`",
                  "hand over the exact-sized buffer"
                ],
                [
                  "`deinit(gpa)`",
                  "free the buffer"
                ]
              ]
            },
            {
              "kind": "warn",
              "content": "Do **not** hold `*T` pointers into `list.items` across `append` — growth may reallocate and invalidate them (see **Pointer Pitfalls**)."
            }
          ]
        },
        {
          "id": "std-hashmaps",
          "title": "HashMaps",
          "summary": "`AutoHashMap` / `StringHashMap` (hashed) and the ArrayHashMap family (insertion order preserved).",
          "keywords": [
            "hashmap",
            "autohashmap",
            "stringhashmap",
            "arrayhashmap",
            "array_hash_map",
            "getorput",
            "getptr",
            "iterator",
            "insertion order"
          ],
          "blocks": [
            {
              "kind": "text",
              "content": "`std.AutoHashMap(K, V)` derives hashing for any hashable `K` (ints, packed structs, ...); `std.StringHashMap(V)` hashes `[]const u8` keys. **ArrayHashMap** variants additionally keep insertion order."
            },
            {
              "kind": "code",
              "title": "Managed map (allocator stored)",
              "code": "var map: std.StringHashMap(u32) = .init(gpa);\ndefer map.deinit();\ntry map.put(\"a\", 1);\nif (map.get(\"a\")) |n| std.debug.print(\"a={d}\\n\", .{ n }); // ?u32\nif (map.getPtr(\"a\")) |ptr| ptr.* += 1; // mutate in place\nvar it = map.iterator();\nwhile (it.next()) |e|\n    std.debug.print(\"{s}={d}\\n\", .{ e.key_ptr.*, e.value_ptr.* });"
            },
            {
              "kind": "table",
              "headers": [
                "type",
                "notes"
              ],
              "rows": [
                [
                  "`std.AutoHashMap(K, V)`",
                  "auto-hashed keys"
                ],
                [
                  "`std.StringHashMap(V)`",
                  "`[]const u8` keys"
                ],
                [
                  "`std.AutoArrayHashMap(K, V)`",
                  "+ insertion order, `.keys()` / `.values()`"
                ],
                [
                  "`std.StringArrayHashMap(V)`",
                  "string keys + order"
                ],
                [
                  "`std.array_hash_map.Auto` / `.String` / `.Custom`",
                  "since 0.16: ArrayHashMap family moved here (managed removed)"
                ]
              ]
            },
            {
              "kind": "list",
              "items": [
                "`*Unmanaged` variants take the allocator per call (like all containers since 0.15)",
                "`get` returns `?V`; `getPtr` returns `?*V` for in-place mutation; `getOrPut` inserts-if-absent",
                "Iterators yield `key_ptr` / `value_ptr` — dereference with `.*`",
                "ArrayHashMap variants preserve **insertion order**; plain HashMap does not"
              ]
            }
          ]
        },
        {
          "id": "std-sort-search",
          "title": "Sorting & Searching",
          "summary": "`std.mem.sort` (stable block sort) and `std.mem.sortUnstable` (pdq) with a context-first comparator.",
          "keywords": [
            "sort",
            "sortunstable",
            "binarysearch",
            "comparator",
            "lessthan",
            "pdq",
            "stable sort"
          ],
          "blocks": [
            {
              "kind": "code",
              "title": "Sort structs by a field, descending",
              "code": "const Score = struct { name: []const u8, score: u32 };\n\nfn byScoreDesc(_: void, a: Score, b: Score) bool {\n    return a.score > b.score; // context comes FIRST (0.15+)\n}\n\nfn demo(items: []Score) void {\n    std.mem.sort(Score, items, {}, byScoreDesc); // stable (block sort)\n    std.mem.sortUnstable(Score, items, {}, byScoreDesc); // pdq, faster\n}"
            },
            {
              "kind": "table",
              "headers": [
                "function",
                "notes"
              ],
              "rows": [
                [
                  "`std.mem.sort(T, items, ctx, lessThan)`",
                  "stable, in-place, O(n log n)"
                ],
                [
                  "`std.mem.sortUnstable(...)`",
                  "pdq sort, not stable, usually faster"
                ],
                [
                  "`std.sort.binarySearch(T, items, ctx, compareFn)`",
                  "requires sorted items — compare-callback shape varies, see std docs for your version"
                ]
              ]
            },
            {
              "kind": "text",
              "content": "`lessThan` has signature `fn (ctx, lhs, rhs) bool` — return `lhs < rhs` for ascending order. Return `>` for descending as above."
            }
          ]
        },
        {
          "id": "std-mem-helpers",
          "title": "std.mem Helpers",
          "summary": "Compare, trim, split, find — with the 0.16 renames (`trimStart`, `find*`, new `cut*`).",
          "ziglings": "109–110",
          "keywords": [
            "std.mem",
            "eql",
            "trim",
            "trimstart",
            "split",
            "tokenize",
            "cut",
            "find",
            "zeroes",
            "parseint"
          ],
          "blocks": [
            {
              "kind": "table",
              "headers": [
                "helper",
                "notes"
              ],
              "rows": [
                [
                  "`eql` / `eqlIgnoreCase`",
                  "slice equality"
                ],
                [
                  "`startsWith` / `endsWith`",
                  "prefix / suffix test"
                ],
                [
                  "`trim` / `trimStart` / `trimEnd`",
                  "cut chars from both/start/end (renamed from trimLeft/trimRight since 0.16)"
                ],
                [
                  "`find` / `findLast`",
                  "indexOf* renamed since 0.16: `indexOf` → `find`, `lastIndexOf` → `findLast`"
                ],
                [
                  "`cut*`",
                  "split at the **first** match — new helpers since 0.16"
                ],
                [
                  "`splitScalar` / `splitSequence` / `tokenizeScalar`",
                  "lazy iterator over parts"
                ],
                [
                  "`replaceOwned` / `join`",
                  "build new strings (allocate)"
                ],
                [
                  "`min` / `max`",
                  "element-wise min/max of slices"
                ],
                [
                  "`zeroes(T)`",
                  "a zeroed `T` value (comptime-known)"
                ]
              ]
            },
            {
              "kind": "code",
              "title": "Everyday std.mem + std.fmt",
              "code": "const t = std.mem.trim(u8, \"  hi \\t\", \" \\t\"); // \"hi\"\nif (std.mem.startsWith(u8, t, \"hi\")) { /* ... */ }\n\nconst n = try std.fmt.parseInt(u32, \"4040\", 10); // !u32\nconst f = try std.fmt.parseFloat(f64, \"3.5\"); // !f64"
            },
            {
              "kind": "text",
              "content": "`splitScalar`/`tokenizeScalar` return iterators — `while (it.next()) |part| { ... }`. `tokenize` skips empty parts, `split` yields them."
            }
          ]
        },
        {
          "id": "std-io-files",
          "title": "Files & Dirs (std.Io)",
          "summary": "Since 0.16 the file system lives behind the `std.Io` interface — every operation takes an `io` handle.",
          "since": "0.16",
          "keywords": [
            "files",
            "dir",
            "readdir",
            "walk",
            "preopen",
            "readfilealloc",
            "createfile",
            "std.io"
          ],
          "blocks": [
            {
              "kind": "text",
              "content": "Since **0.16** the file system is part of the `std.Io` interface: `std.Io.Dir` and `std.Io.File` operations take an `io` handle (obtained from `main(init)`)."
            },
            {
              "kind": "code",
              "title": "Read and write a file",
              "code": "// read, bounded to 1 MiB:\nconst bytes = try std.Io.Dir.cwd().readFileAlloc(\n    io, \"input.txt\", gpa, .limited(1 << 20),\n);\ndefer gpa.free(bytes);\n\n// write:\nvar f = try std.Io.Dir.cwd().createFile(io, \"out.txt\", .{});\ndefer f.close(io);\ntry f.writeStreamingAll(io, \"hi\\n\");",
              "note": "0.16 API — exact signatures may shift in patch releases; consult the std docs for your version."
            },
            {
              "kind": "list",
              "items": [
                "`std.Io.Dir.cwd()` — current directory handle; `openDir(io, path, .{})` for others",
                "Directory iteration and recursive `walk` (selective walking since 0.16)",
                "**Preopens** (WASI-style, since 0.16): directories are capabilities handed to the program",
                "`std.Io.File.stdout()` / `.stderr()` — standard streams"
              ]
            }
          ]
        },
        {
          "id": "std-process-time",
          "title": "Process, Args, Env, Time & Random (0.16)",
          "summary": "All process-level services are non-global since 0.16 — they arrive via `main(init: std.process.Init)`.",
          "since": "0.16",
          "keywords": [
            "process",
            "args",
            "environ",
            "spawn",
            "child",
            "clock",
            "duration",
            "timestamp",
            "random",
            "juicy main"
          ],
          "blocks": [
            {
              "kind": "text",
              "content": "Since **0.16** args, environ, spawning, time and randomness are **non-global**: they are capabilities passed to `pub fn main(init: std.process.Init)` (\"Juicy main\"). An empty `pub fn main()` is still legal."
            },
            {
              "kind": "code",
              "title": "Args and environ from init",
              "code": "pub fn main(init: std.process.Init) !void {\n    const args = try init.minimal.args.toSlice(init.arena.allocator());\n    const user = init.environ_map.get(\"USER\") orelse \"nobody\";\n    std.debug.print(\"{s}: {d} args\\n\", .{ user, args.len });\n}"
            },
            {
              "kind": "code",
              "title": "Running a child process",
              "code": "const result = try std.process.run(gpa, io, .{ .argv = &.{ \"git\", \"status\" } });\nstd.debug.print(\"{s}\", .{result.stdout});"
            },
            {
              "kind": "table",
              "headers": [
                "service",
                "0.16 access"
              ],
              "rows": [
                [
                  "args",
                  "`init.minimal.args.toSlice(init.arena.allocator())`"
                ],
                [
                  "environment",
                  "`init.environ_map.get(\"KEY\")` (`.keys()` / `.values()`)"
                ],
                [
                  "children",
                  "`std.process.run(gpa, io, .{ .argv = ... })`"
                ],
                [
                  "time",
                  "`std.Io.Clock` / `Timestamp` / `Duration` types — `Duration` formats with `{f}`"
                ],
                [
                  "randomness",
                  "moved to the `std.Io` interface since 0.16 — see the io docs"
                ]
              ]
            },
            {
              "kind": "text",
              "content": "For args+environ only, `std.process.Init.Minimal` is the lightweight variant of the init struct."
            }
          ]
        }
      ]
    },
    {
      "id": "testing",
      "title": "Testing",
      "icon": "FlaskConical",
      "blurb": "Built-in test framework: no dependencies, leak detection by default, fuzzing in the toolchain.",
      "entries": [
        {
          "id": "test-basics",
          "title": "Test Blocks",
          "summary": "Any `test \"...\" { ... }` block runs under `zig test` — assertions come from `std.testing`.",
          "ziglings": "105",
          "keywords": [
            "test block",
            "std.testing",
            "expect",
            "expectequal",
            "expectequalstrings",
            "expecterror",
            "expectfmt",
            "assert"
          ],
          "blocks": [
            {
              "kind": "code",
              "title": "A test next to the code it tests",
              "code": "const std = @import(\"std\");\nconst testing = std.testing;\n\nfn add(a: i32, b: i32) i32 {\n    return a + b;\n}\n\ntest \"add works\" {\n    try testing.expect(add(2, 2) == 4);\n    try testing.expectEqual(@as(i32, 4), add(2, 2));\n    try testing.expectFmt(\"4\", \"{d}\", .{add(2, 2)});\n}"
            },
            {
              "kind": "text",
              "content": "Tests live **next to the code** — in the same file, or in a separate test file that imports it. `zig test math.zig` finds and runs every `test` block. Assertions return `!void`, so prefix them with `try`."
            },
            {
              "kind": "table",
              "headers": [
                "helper",
                "checks"
              ],
              "rows": [
                [
                  "`expect(cond)`",
                  "condition is true"
                ],
                [
                  "`expectEqual(expected, actual)`",
                  "deep equality (structs, arrays)"
                ],
                [
                  "`expectEqualStrings(a, b)`",
                  "string content + length"
                ],
                [
                  "`expectEqualSlices(T, a, b)`",
                  "element-wise slice equality"
                ],
                [
                  "`expectError(err, expr)`",
                  "expr fails with exactly `err`"
                ],
                [
                  "`expectApproxEqAbs` / `Relative`",
                  "float comparison with tolerance"
                ],
                [
                  "`expectFmt(expected, fmt, args)`",
                  "value formats to expected text"
                ]
              ]
            },
            {
              "kind": "text",
              "content": "Note that unreferenced functions are not analyzed — a test that never calls `f` will not type-check `f`'s body. Use `refAllDecls` (next entry)."
            }
          ]
        },
        {
          "id": "test-runner",
          "title": "Running & Organizing",
          "summary": "`zig test` for files, `zig build test` for projects, `refAllDecls` to force analysis.",
          "keywords": [
            "zig test",
            "test filter",
            "refalldecls",
            "build test",
            "organize tests",
            "test root"
          ],
          "blocks": [
            {
              "kind": "code",
              "title": "A test root that pulls in everything",
              "code": "// src/all.zig\ntest {\n    std.testing.refAllDecls(@This()); // force analysis of all decls\n    _ = @import(\"parser.zig\"); // and other files' tests\n    _ = @import(\"json.zig\");\n}"
            },
            {
              "kind": "list",
              "items": [
                "`zig test math.zig` — run one file's tests",
                "`zig test src/all.zig` — run a project via an aggregating root file",
                "`zig build test` — through a test step in build.zig (see **Test Step**)",
                "`zig test --test-filter \"json\" src/all.zig` — only matching test names",
                "Name tests descriptively: `test \"json: parses empty object\"`"
              ]
            },
            {
              "kind": "text",
              "content": "On failure the default runner prints the expression, expected vs actual, and a stack trace. Add `--summary all` (with `zig build`) for a per-step breakdown."
            }
          ]
        },
        {
          "id": "test-alloc",
          "title": "Allocator-Aware Tests",
          "summary": "`std.testing.allocator` detects leaks per test — a leak fails the test with a stack trace.",
          "ziglings": "105",
          "keywords": [
            "testing.allocator",
            "leak detection",
            "arena",
            "deinit",
            "expectequal deep",
            "failing test"
          ],
          "blocks": [
            {
              "kind": "code",
              "title": "A leak fails the test",
              "code": "test \"leaks fail the test\" {\n    const a = std.testing.allocator;\n    const buf = try a.alloc(u8, 16);\n    _ = buf;\n    // oops: no \"defer a.free(buf);\"\n    // -> FAILS with a leak report (address + stack trace)\n}"
            },
            {
              "kind": "code",
              "title": "Arena for scratch data in tests",
              "code": "test \"arena for scratch\" {\n    var arena = std.heap.ArenaAllocator.init(std.testing.allocator);\n    defer arena.deinit();\n    const a = arena.allocator();\n    const s = try std.fmt.allocPrint(a, \"{d}\", .{42});\n    try std.testing.expectEqualStrings(\"42\", s);\n}"
            },
            {
              "kind": "text",
              "content": "`std.testing.allocator` is a leak-checking wrapper around `DebugAllocator`. `expectEqual` compares **deeply** for structs, unions and arrays — no per-field assertions needed."
            }
          ]
        },
        {
          "id": "test-fuzz",
          "title": "Fuzzing",
          "summary": "Fuzz targets are ordinary tests — `zig build test --fuzz` runs them coverage-guided.",
          "keywords": [
            "fuzz",
            "fuzzing",
            "coverage guided",
            "ast smith",
            "multiprocess",
            "crash",
            "getfuzzinput"
          ],
          "blocks": [
            {
              "kind": "code",
              "title": "A fuzz target (see std.testing.fuzz for your version)",
              "code": "test \"parser never crashes on garbage\" {\n    const ctx = {};\n    std.testing.fuzz(ctx, struct {\n        fn f(_: @TypeOf(ctx), input: []const u8) void {\n            _ = parse(input) catch {}; // any crash = fuzz failure\n        }\n    }.f, .{});\n}",
              "note": "The `std.testing.fuzz` API is still evolving — check the std docs for your version."
            },
            {
              "kind": "list",
              "items": [
                "`zig build test --fuzz` — coverage-guided fuzzing (since 0.14)",
                "Fuzz targets are **normal test blocks**; safe build modes turn memory errors into catchable crashes",
                "Run it locally against your parser / decoder / any `[]const u8` consumer"
              ]
            },
            {
              "kind": "list",
              "items": [
                "0.16 fuzzing improvements include:",
                "AST smith — grammar-aware structured input generation",
                "multiprocess fuzzing",
                "infinite mode",
                "crash dumps for reproduction"
              ]
            }
          ]
        }
      ]
    },
    {
      "id": "build-system",
      "title": "Build System",
      "icon": "Hammer",
      "blurb": "build.zig is Zig: declarative steps, cross-compilation and package management built in.",
      "entries": [
        {
          "id": "build-minimal",
          "title": "Minimal build.zig (0.16)",
          "summary": "Target + optimize + one executable, installed with `zig build`, run with `zig build run`.",
          "since": "0.16",
          "keywords": [
            "build.zig",
            "addexecutable",
            "createmodule",
            "root_module",
            "standardtargetoptions",
            "standardoptimizeoption",
            "installartifact"
          ],
          "blocks": [
            {
              "kind": "code",
              "title": "build.zig",
              "code": "const std = @import(\"std\");\n\npub fn build(b: *std.Build) void {\n    const target = b.standardTargetOptions(.{});\n    const optimize = b.standardOptimizeOption(.{});\n\n    const exe = b.addExecutable(.{\n        .name = \"app\",\n        .root_module = b.createModule(.{\n            .root_source_file = b.path(\"src/main.zig\"),\n            .target = target,\n            .optimize = optimize,\n        }),\n    });\n    b.installArtifact(exe);\n\n    const run_cmd = b.addRunArtifact(exe);\n    const run_step = b.step(\"run\", \"Run the app\");\n    run_step.dependOn(&run_cmd.step);\n}"
            },
            {
              "kind": "text",
              "content": "Modern style (0.15+) nests `.root_source_file`, `.target` and `.optimize` inside `root_module = b.createModule(...)`; setting them directly on the executable options is the legacy shape."
            },
            {
              "kind": "list",
              "items": [
                "`zig build` — default step: compile and install to `zig-out/bin`",
                "`zig build run` — run the `run` step defined above",
                "`--release=fast` — optimized build without editing anything"
              ]
            }
          ]
        },
        {
          "id": "build-steps",
          "title": "Steps, Options & Artifacts",
          "summary": "Custom `-D` options, named steps, and the artifact kinds the build graph understands.",
          "keywords": [
            "step",
            "option",
            "flags",
            "release",
            "target",
            "prefix",
            "install",
            "addlibrary",
            "runartifact"
          ],
          "blocks": [
            {
              "kind": "code",
              "title": "Options and steps",
              "code": "const level = b.option(u32, \"level\", \"compression level 0-9\") orelse 6;\nconst target = b.standardTargetOptions(.{}); // -Dtarget, -Dcpu\nconst optimize = b.standardOptimizeOption(.{}); // --release=...\nconst t = b.step(\"test\", \"Run tests\"); // zig build test"
            },
            {
              "kind": "table",
              "headers": [
                "invocation",
                "effect"
              ],
              "rows": [
                [
                  "`zig build`",
                  "default step: install artifacts"
                ],
                [
                  "`zig build run`",
                  "run a custom step"
                ],
                [
                  "`zig build test --summary all`",
                  "test step + full failure summary"
                ],
                [
                  "`--release=safe|fast|small`",
                  "optimize mode (Debug if unset)"
                ],
                [
                  "`-Doptimize=ReleaseSafe`",
                  "same, via the standard option"
                ],
                [
                  "`-Dtarget=x86_64-windows-gnu`",
                  "cross-compile target triple"
                ],
                [
                  "`-Dlevel=9`",
                  "your own `b.option` values"
                ],
                [
                  "`--prefix ./out`",
                  "install directory instead of `zig-out`"
                ]
              ]
            },
            {
              "kind": "list",
              "items": [
                "Artifacts: `b.addExecutable(...)` / `b.addLibrary(...)` / `b.addObject(...)`",
                "`b.installArtifact(artifact)` attaches it to the default install step",
                "`b.addRunArtifact(...)` runs executables **and** tests as a build step",
                "`b.default_step` — what plain `zig build` runs"
              ]
            }
          ]
        },
        {
          "id": "build-modules",
          "title": "Modules & Dependencies",
          "summary": "Wire multi-file projects with `createModule` + imports; fetch packages via build.zig.zon.",
          "keywords": [
            "module",
            "import",
            "dependency",
            "build.zig.zon",
            "zig fetch",
            "fingerprint",
            "package"
          ],
          "blocks": [
            {
              "kind": "code",
              "title": "Two modules, one import",
              "code": "const lib_mod = b.createModule(.{\n    .root_source_file = b.path(\"src/lib.zig\"),\n    .target = target,\n    .optimize = optimize,\n});\nconst exe_mod = b.createModule(.{\n    .root_source_file = b.path(\"src/main.zig\"),\n    .target = target,\n    .optimize = optimize,\n    .imports = &.{\n        .{ .name = \"lib\", .module = lib_mod },\n    },\n});\n// in main.zig:  const lib = @import(\"lib\");"
            },
            {
              "kind": "code",
              "title": "Package dependency from build.zig.zon",
              "code": "// build.zig.zon:\n//   .dependencies = .{ .lib = .{ .url = \"https://...\", .hash = \"...\" } },\nconst dep = b.dependency(\"lib\", .{});\nexe.root_module.addImport(\"lib\", dep.module(\"lib\"));\n// add deps with:  zig fetch --save <url-or-tarball>"
            },
            {
              "kind": "table",
              "headers": [
                "zon field",
                "notes"
              ],
              "rows": [
                [
                  "`.name`",
                  "enum literal since 0.14: `.my_project`"
                ],
                [
                  "`.version`",
                  "semver string, e.g. `\"0.1.0\"`"
                ],
                [
                  "`.fingerprint`",
                  "package identity hash (0.14+)"
                ],
                [
                  "`.minimum_zig_version`",
                  "oldest Zig that can build this"
                ],
                [
                  "`.dependencies`",
                  "map of `{ .url, .hash }` (or local path) deps"
                ],
                [
                  "`.paths`",
                  "files/dirs included when the package is used"
                ]
              ]
            }
          ]
        },
        {
          "id": "build-c",
          "title": "Linking & C Files",
          "summary": "linkLibC, system libraries, include paths, .c sources — and translate-c for headers.",
          "keywords": [
            "linklibc",
            "linksystemlibrary",
            "includepath",
            "csourcefiles",
            "addtranslatec",
            "translate-c",
            "cimport"
          ],
          "blocks": [
            {
              "kind": "code",
              "title": "Mixing C into a Zig build",
              "code": "exe.linkLibC(); // -lc\nexe.linkSystemLibrary(\"sdl2\"); // -lSDL2\nexe.addIncludePath(b.path(\"include\")); // -I\nexe.addCSourceFiles(.{\n    .files = &.{ \"vendor/foo.c\", \"vendor/bar.c\" },\n    .flags = &.{},\n});"
            },
            {
              "kind": "code",
              "title": "translate-c for headers (0.16)",
              "code": "const tc = b.addTranslateC(.{\n    .root_source_file = b.path(\"src/bindings.h\"),\n    .target = target,\n    .optimize = optimize,\n});\nexe.root_module.addImport(\"c\", tc.createModule());",
              "note": "`@cImport` is deprecated since **0.16** — this build-based flow replaces it (see **C Interop**)."
            }
          ]
        },
        {
          "id": "build-test",
          "title": "Test Step",
          "summary": "The standard `zig build test` recipe: addTest + addRunArtifact + a named step.",
          "keywords": [
            "test step",
            "addtest",
            "runartifact",
            "unit test timeout",
            "zig build test",
            "fuzz"
          ],
          "blocks": [
            {
              "kind": "code",
              "title": "Standard test step",
              "code": "const tests = b.addTest(.{ .root_module = mod });\nconst run_tests = b.addRunArtifact(tests);\nconst t = b.step(\"test\", \"Run unit tests\");\nt.dependOn(&run_tests.step);"
            },
            {
              "kind": "list",
              "items": [
                "`addTest` analyzes every `test` block reachable from the module root",
                "Force analysis of leaf decls with `std.testing.refAllDecls` in your code (see **Testing**)",
                "Run: `zig build test`; add `--summary all` for details",
                "`zig build test --fuzz` — fuzz mode (see **Fuzzing**)",
                "Unit-test **timeouts** are available as an option since 0.16"
              ]
            },
            {
              "kind": "text",
              "content": "The test step depends on the same module graph as your exe — tests compile with the target and optimize settings of the module they test."
            }
          ]
        }
      ]
    },
    {
      "id": "c-interop",
      "title": "C Interop",
      "icon": "Plug",
      "blurb": "Call C, export Zig, translate headers, and cross-compile C itself — no glue layer needed.",
      "entries": [
        {
          "id": "c-types",
          "title": "C Types & [*c] Pointers",
          "summary": "`c_int` and friends for C-width integers; `[*c]T` for nullable C pointers of unknown length.",
          "ziglings": "096–097",
          "keywords": [
            "c_int",
            "c_char",
            "c pointer",
            "star c",
            "span",
            "sentinel",
            "nullable",
            "abi"
          ],
          "blocks": [
            {
              "kind": "table",
              "headers": [
                "Zig type",
                "C equivalent"
              ],
              "rows": [
                [
                  "`c_char`, `c_short`, `c_int`, `c_long`, `c_longlong`",
                  "`char`, `short`, `int`, `long`, `long long`"
                ],
                [
                  "`c_uint`, `c_ushort`, `c_ulong`, `c_ulonglong`",
                  "unsigned variants"
                ],
                [
                  "`c_longdouble`",
                  "`long double`"
                ],
                [
                  "`bool`",
                  "`bool` (C99 `_Bool`)"
                ],
                [
                  "`usize`",
                  "`size_t`"
                ],
                [
                  "`f32` / `f64`",
                  "`float` / `double` (fixed width)"
                ]
              ]
            },
            {
              "kind": "text",
              "content": "`[*c]T` is the **C pointer**: nullable, `allowzero`, coerces in both directions with `[*]T`, `[*:0]T` and `?[*]T`, and has **unknown length** (no indexing, no `.len`). A null check works through optional coercion: `if (cptr) |p| ...`."
            },
            {
              "kind": "code",
              "title": "C pointers in practice",
              "code": "extern fn strlen(s: [*c]const u8) usize;\n\nfn safeLen(s: [*c]const u8) usize {\n    if (s) |p| return strlen(p); // null check via optional coercion\n    return 0;\n}\n\n// sentinel-terminated C string -> slice:\n//   const slice: []const u8 = std.mem.span(cstr);"
            },
            {
              "kind": "text",
              "content": "`std.mem.span(cstr)` converts a `[*:0]const u8` into a `[]const u8` by counting to the sentinel — the bridge between C strings and Zig slices."
            }
          ]
        },
        {
          "id": "c-extern",
          "title": "Calling C Functions",
          "summary": "Declare `extern` fns yourself, link libc, done — headers optional when you import a translate-c module.",
          "ziglings": "096–097",
          "keywords": [
            "extern",
            "libc",
            "puts",
            "variadic",
            "printf",
            "callconv",
            "linksystemlibrary",
            "linklibc"
          ],
          "blocks": [
            {
              "kind": "code",
              "title": "extern declarations",
              "code": "extern \"c\" fn puts(s: [*:0]const u8) c_int;\nextern fn abs(x: c_int) c_int; // \"c\" implied when linking libc\nextern \"c\" fn printf(fmt: [*:0]const u8, ...) c_int; // variadic\n\npub fn main() void {\n    _ = puts(\"hello from zig\");\n    _ = abs(-42);\n    _ = printf(\"n=%d\\n\", @as(c_int, 7));\n}"
            },
            {
              "kind": "list",
              "items": [
                "Library name optional: `extern fn ...` defaults to the C ABI of the linked libc",
                "Variadic: `...` in the parameter list, like C",
                "Functions passed **into** C use the C calling convention: `fn f() callconv(.c) void`"
              ]
            },
            {
              "kind": "code",
              "title": "Linking",
              "code": "// build.zig:\nexe.linkLibC();\nexe.linkSystemLibrary(\"m\"); // libm, SDL2, ... anything pkg-config finds\n\n// command line:\n//   zig build-exe main.zig -lc"
            }
          ]
        },
        {
          "id": "c-export",
          "title": "Exporting Zig for C",
          "summary": "`export fn` emits C-ABI symbols; `extern struct`/`union`/`enum` fix the ABI layout; callbacks via `callconv(.c)`.",
          "keywords": [
            "export",
            "extern struct",
            "callback",
            "callconv",
            "at-export",
            "build-lib",
            "dynamic library"
          ],
          "blocks": [
            {
              "kind": "code",
              "title": "Exported functions and symbol control",
              "code": "export fn add(a: c_int, b: c_int) c_int {\n    return a + b; // symbol \"add\" lands in the object file\n}\n\nfn mul(a: c_int, b: c_int) callconv(.c) c_int {\n    return a * b;\n}\n\ncomptime {\n    @export(&mul, .{ .name = \"my_mul\" }); // custom symbol name\n}"
            },
            {
              "kind": "text",
              "content": "`extern struct`, `extern union` and `extern enum(c_int)` guarantee C ABI field layout — use them for types shared across the boundary. Plain Zig structs may reorder/pad differently."
            },
            {
              "kind": "code",
              "title": "Passing a Zig callback into C",
              "code": "const Callback = *const fn (ctx: ?*anyopaque, n: c_int) callconv(.c) void;\nextern \"c\" fn register(cb: Callback) void;\n\nfn onEvent(ctx: ?*anyopaque, n: c_int) callconv(.c) void {\n    _ = ctx;\n    std.debug.print(\"event {d}\\n\", .{n});\n}\n\npub fn init() void {\n    register(onEvent);\n}"
            },
            {
              "kind": "list",
              "items": [
                "`zig build-lib lib.zig` — static library",
                "`zig build-lib -dynamic lib.zig` — shared library",
                "The C header for your exports must be written by hand"
              ]
            }
          ]
        },
        {
          "id": "c-translate",
          "title": "Headers: translate-c (0.16)",
          "summary": "`@cImport` is deprecated — translate headers in build.zig and import them as a module.",
          "since": "0.16",
          "keywords": [
            "translate-c",
            "cimport",
            "header",
            "bindings",
            "addtranslatec",
            "cinclude",
            "deprecated"
          ],
          "blocks": [
            {
              "kind": "compare",
              "left": {
                "title": "0.15 and earlier — @cImport (deprecated)",
                "lang": "zig",
                "code": "const c = @cImport({\n    @cInclude(\"sqlite3.h\");\n});\n\nconst rc = c.sqlite3_open(\":memory:\", &db);"
              },
              "right": {
                "title": "0.16 — build-based translate-c",
                "lang": "zig",
                "code": "// build.zig:\nconst tc = b.addTranslateC(.{\n    .root_source_file = b.path(\"vendor/sqlite3.h\"),\n    .target = target,\n    .optimize = optimize,\n});\nexe.root_module.addImport(\"c\", tc.createModule());\n\n// main.zig:\n// const c = @import(\"c\");"
              },
              "note": "Same result — but the 0.16 way is explicit in the build graph and caches per header."
            },
            {
              "kind": "list",
              "items": [
                "One-shot CLI: `zig translate-c vendor/sqlite3.h > bindings.zig`, then `@import(\"bindings.zig\")`",
                "Include paths and defines can be given on the translate-c step",
                "Vendoring the generated `bindings.zig` avoids a build step entirely"
              ]
            },
            {
              "kind": "warn",
              "content": "`@cImport` still compiles in 0.16 but is **deprecated** — migrate new code to `b.addTranslateC` now."
            }
          ]
        },
        {
          "id": "c-zigcc",
          "title": "zig cc — C/C++ Toolchain",
          "summary": "Zig ships a full clang-based C/C++ compiler with cross-compilation and bundled libc targets.",
          "keywords": [
            "zig cc",
            "zig c++",
            "clang",
            "cross compile",
            "musl",
            "glibc",
            "mingw",
            "makefile"
          ],
          "blocks": [
            {
              "kind": "code",
              "title": "sh",
              "lang": "sh",
              "code": "zig cc main.c -o main              # drop-in clang/gcc replacement\nzig cc -target aarch64-linux-gnu main.c -o main  # instant cross-compile\nzig c++ main.cpp -o main           # C++ too"
            },
            {
              "kind": "list",
              "items": [
                "libc headers and libraries are **bundled**: musl, glibc, mingw-w64 — no system toolchain required",
                "Same target-triple syntax as `zig build -Dtarget=...`",
                "Ideal for building C dependencies and Makefile-free builds",
                "Flags are clang-compatible: `-I`, `-L`, `-l`, `-static`, ..."
              ]
            },
            {
              "kind": "code",
              "title": "sh",
              "lang": "sh",
              "code": "# build a C library once, for two targets, no Docker:\nzig cc -c -Iinclude vendor/foo.c -target x86_64-linux-gnu -o foo-linux.o\nzig cc -c -Iinclude vendor/foo.c -target aarch64-macos-gnu -o foo-mac.o"
            }
          ]
        }
      ]
    },
    {
      "id": "concurrency",
      "title": "Concurrency",
      "icon": "Layers",
      "blurb": "Threads, locks and atomics — plus the 0.16 std.Io interface that replaced async/await.",
      "entries": [
        {
          "id": "conc-threads",
          "title": "Threads",
          "summary": "`std.Thread.spawn` with any function and argument tuple; join or detach; shared state is your job.",
          "ziglings": "107–108",
          "keywords": [
            "thread",
            "spawn",
            "join",
            "detach",
            "getcpucount",
            "parallelism",
            "data race"
          ],
          "blocks": [
            {
              "kind": "code",
              "title": "Spawn + join with a context struct",
              "code": "const Job = struct { n: u32, out: u32 = 0 };\n\nfn worker(job: *Job) void {\n    job.out = job.n * 2;\n}\n\npub fn main() !void {\n    var job: Job = .{ .n = 21 };\n    const t = try std.Thread.spawn(.{}, worker, .{&job});\n    t.join(); // block until done (or: t.detach())\n    std.debug.print(\"{d}\\n\", .{job.out}); // 42\n}"
            },
            {
              "kind": "table",
              "headers": [
                "operation",
                "notes"
              ],
              "rows": [
                [
                  "`std.Thread.spawn(.{}, f, .{args})`",
                  "returns `!Thread` — options struct first"
                ],
                [
                  "`t.join()`",
                  "block until the thread finishes"
                ],
                [
                  "`t.detach()`",
                  "let it run free; never join it"
                ],
                [
                  "`std.Thread.current()`",
                  "handle for the calling thread"
                ],
                [
                  "`std.Thread.getCpuCount()`",
                  "`!usize` — hardware parallelism"
                ]
              ]
            },
            {
              "kind": "warn",
              "content": "**Data races are UB.** Sharing mutable state across threads requires synchronization (locks) or atomics — the compiler will not stop you, so route anything shared through one of the next two entries."
            }
          ]
        },
        {
          "id": "conc-locks",
          "title": "Mutex, Condition & Friends",
          "summary": "The `std.Thread` synchronization primitives: Mutex, Condition, ResetEvent, Semaphore, RwLock.",
          "keywords": [
            "mutex",
            "condition",
            "resetevent",
            "semaphore",
            "rwlock",
            "lock",
            "trylock",
            "wait",
            "signal",
            "broadcast"
          ],
          "blocks": [
            {
              "kind": "table",
              "headers": [
                "primitive",
                "operations"
              ],
              "rows": [
                [
                  "`std.Thread.Mutex`",
                  "`lock` / `unlock` / `tryLock`; shared mode: `lockShared` / `unlockShared`"
                ],
                [
                  "`std.Thread.Condition`",
                  "`wait(&mutex)` / `signal` / `broadcast` / `timedWait`"
                ],
                [
                  "`std.Thread.ResetEvent`",
                  "`wait` / `set` / `reset` / `isSet`"
                ],
                [
                  "`std.Thread.Semaphore`",
                  "`wait` / `post`"
                ],
                [
                  "`std.Thread.RwLock`",
                  "`lock` / `unlock` / `lockShared` / `unlockShared`"
                ]
              ]
            },
            {
              "kind": "code",
              "title": "Guard pattern: mutex next to its data",
              "code": "const Counter = struct {\n    mu: std.Thread.Mutex = .{},\n    n: u32 = 0,\n\n    fn bump(self: *Counter) void {\n        self.mu.lock();\n        defer self.mu.unlock(); // always unlock, even on early return\n        self.n += 1; // protected critical section\n    }\n};"
            },
            {
              "kind": "code",
              "title": "ResetEvent: one-shot done signal",
              "code": "var done: std.Thread.ResetEvent = .{};\n\nfn work() void {\n    defer done.set();\n    // ... do the work ...\n}\n\n// another thread:\ndone.wait(); // blocks until set()"
            }
          ]
        },
        {
          "id": "conc-atomics",
          "title": "Atomics",
          "summary": "`std.atomic.Value(T)` wraps one value with explicit memory ordering; raw `@atomic*` builtins below it.",
          "keywords": [
            "atomic",
            "atomic.value",
            "load",
            "store",
            "rmw",
            "cmpxchg",
            "seq_cst",
            "acquire",
            "release",
            "fence"
          ],
          "blocks": [
            {
              "kind": "code",
              "title": "Stop flag across threads",
              "code": "var stop: std.atomic.Value(bool) = .init(false);\n\nfn worker() void {\n    while (!stop.load(.acquire)) {\n        // ... work chunk ...\n    }\n}\n\n// main thread:\nstop.store(true, .release); // signal shutdown"
            },
            {
              "kind": "table",
              "headers": [
                "`std.atomic.Value(T)` op",
                "call"
              ],
              "rows": [
                [
                  "create",
                  "`std.atomic.Value(T).init(v)`"
                ],
                [
                  "read",
                  "`.load(.acquire)`"
                ],
                [
                  "write",
                  "`.store(v, .release)`"
                ],
                [
                  "exchange",
                  "`.swap(v, .seq_cst)`"
                ],
                [
                  "read-modify-write",
                  "`.rmw(.Add, x, .seq_cst)`"
                ]
              ]
            },
            {
              "kind": "table",
              "headers": [
                "raw builtin",
                "purpose"
              ],
              "rows": [
                [
                  "`@atomicLoad(T, ptr, order)`",
                  "atomic read"
                ],
                [
                  "`@atomicStore(T, ptr, v, order)`",
                  "atomic write"
                ],
                [
                  "`@atomicRmw(T, ptr, op, v, order)`",
                  "atomic read-modify-write"
                ],
                [
                  "`@cmpxchgStrong` / `@cmpxchgWeak`",
                  "compare-and-swap (weak may spuriously fail)"
                ],
                [
                  "`@fence(order)`",
                  "standalone memory barrier"
                ]
              ]
            },
            {
              "kind": "list",
              "items": [
                "Memory orders: `.monotonic` (relaxed), `.acquire`, `.release`, `.acq_rel`, `.seq_cst`",
                "Default to `.seq_cst`; relax only with profiling evidence",
                "`.monotonic` for counters, `.acquire`/`.release` pairing for flag hand-offs"
              ]
            }
          ]
        },
        {
          "id": "conc-io",
          "title": "std.Io: The Async Story (0.16)",
          "summary": "async/await keywords are gone — concurrency is an interface (`std.Io`) with pluggable implementations.",
          "since": "0.16",
          "ziglings": "085–095",
          "keywords": [
            "async",
            "await",
            "std.io",
            "future",
            "group",
            "queue",
            "batch",
            "select",
            "threaded",
            "evented"
          ],
          "blocks": [
            {
              "kind": "warn",
              "title": "History callout",
              "content": "The `async` / `await` / `suspend` / `resume` keywords were **removed since 0.14**. Since **0.16**, I/O concurrency is expressed through the `std.Io` interface instead — an `io` handle flows through your program."
            },
            {
              "kind": "table",
              "headers": [
                "abstraction",
                "role"
              ],
              "rows": [
                [
                  "`std.Io.Future`",
                  "one task started from a function; await its result"
                ],
                [
                  "`std.Io.Group`",
                  "many tasks; await or cancel all of them"
                ],
                [
                  "`std.Io.Queue(T)`",
                  "MPMC channel between tasks"
                ],
                [
                  "`std.Io.Batch`",
                  "batch many operations, submit together"
                ],
                [
                  "`std.Io.Select`",
                  "wait on several operations at once"
                ]
              ]
            },
            {
              "kind": "table",
              "headers": [
                "implementation",
                "notes"
              ],
              "rows": [
                [
                  "`std.Io.Threaded`",
                  "default — operations block on a thread pool"
                ],
                [
                  "`std.Io.Evented`",
                  "experimental green threads"
                ],
                [
                  "`std.Io.Uring` / `Kqueue` / `Dispatch`",
                  "proof-of-concept backends"
                ],
                [
                  "`std.Io.failing`",
                  "always fails — tests, no-IO builds"
                ]
              ]
            },
            {
              "kind": "code",
              "title": "Illustrative shape (simplified)",
              "code": "// SIMPLIFIED - the 0.16 std.Io API is evolving:\n// consult the std docs for your exact version.\nconst fut = try io.async(work, .{ arg }); // start a task\nconst out = try fut.await(io); // join it\n\n// many tasks: std.Io.Group\n//   g.async(io, work, .{...}) for each, then g.await(io) / g.cancel(io)"
            },
            {
              "kind": "list",
              "items": [
                "`-fsingle-threaded` — compile assuming one thread (enables optimizations, disables thread APIs)",
                "`-fno-single-threaded` — force the opposite",
                "Which backend runs is configured per root module — see the std.Io docs for your version"
              ]
            }
          ]
        },
        {
          "id": "conc-guidance",
          "title": "Choosing a Tool",
          "summary": "CPU parallelism → threads; I/O concurrency → std.Io; signaling → events; counters → atomics.",
          "keywords": [
            "decision",
            "guidance",
            "thread vs async",
            "when to use",
            "signaling",
            "counters",
            "ziglings history"
          ],
          "blocks": [
            {
              "kind": "table",
              "headers": [
                "you need",
                "reach for"
              ],
              "rows": [
                [
                  "CPU parallelism",
                  "`std.Thread` (+ locks / atomics)"
                ],
                [
                  "I/O concurrency",
                  "`std.Io` — `Future` / `Group` / `Queue(T)`"
                ],
                [
                  "simple signaling",
                  "`ResetEvent` or `Condition`"
                ],
                [
                  "counters / stop flags",
                  "`std.atomic.Value(T)`"
                ],
                [
                  "per-task scratch memory",
                  "one `ArenaAllocator` per task"
                ]
              ]
            },
            {
              "kind": "text",
              "content": "Start with `std.Thread` for CPU-bound work. `std.Io.Threaded` gives I/O concurrency without changing your threading model; `std.Io.Evented` scales to many concurrent I/O operations on few threads."
            },
            {
              "kind": "warn",
              "content": "Ziglings exercises **085–095** teach the **removed** `async`/`await` keywords — treat them as historical. Ziglings exercises for the new `std.Io` interface are still pending."
            }
          ]
        }
      ]
    },
    {
      "id": "gotchas",
      "title": "Gotchas & Tips",
      "icon": "ShieldAlert",
      "blurb": "Where Zig bites: UB, undefined, version churn — and the philosophy behind the rules.",
      "entries": [
        {
          "id": "got-safety",
          "title": "Safety Checks vs UB",
          "summary": "Debug and ReleaseSafe panic with a trace; ReleaseFast turns failed checks into UB; ReleaseSmall mostly drops them.",
          "keywords": [
            "safety",
            "bounds check",
            "overflow",
            "unreachable",
            "releasefast",
            "releasesafe",
            "releasesmall",
            "undefined behavior"
          ],
          "blocks": [
            {
              "kind": "table",
              "headers": [
                "check",
                "Debug / ReleaseSafe",
                "ReleaseFast",
                "ReleaseSmall"
              ],
              "rows": [
                [
                  "array/slice bounds",
                  "panic + trace",
                  "UB",
                  "off"
                ],
                [
                  "integer overflow",
                  "panic + trace",
                  "UB",
                  "wrapping (off)"
                ],
                [
                  "`.?` on null",
                  "panic + trace",
                  "UB",
                  "off"
                ],
                [
                  "`unreachable` reached",
                  "panic + trace",
                  "UB",
                  "off"
                ],
                [
                  "division by zero",
                  "panic + trace",
                  "UB",
                  "off"
                ],
                [
                  "cast truncation (`@intCast`)",
                  "panic + trace",
                  "UB",
                  "off"
                ]
              ]
            },
            {
              "kind": "text",
              "content": "Rule: **develop and test in Debug or ReleaseSafe**, ship ReleaseFast only with solid tests and coverage. ReleaseSmall trades most safety checks for binary size (overflow wraps silently)."
            },
            {
              "kind": "list",
              "items": [
                "`unreachable` is a promise to the optimizer — keep it true in every mode",
                "`@intCast` and `.?` are checked in safe modes, unchecked elsewhere",
                "Out-of-range `@intFromFloat` is UB — range-check before converting"
              ]
            }
          ]
        },
        {
          "id": "got-undefined",
          "title": "undefined & Uninitialized",
          "summary": "`undefined` means \"no value\" — reading it is UB in every build mode (0xAA fill makes it visible in Debug).",
          "keywords": [
            "undefined",
            "uninitialized",
            "0xaa",
            "memset",
            "defer init",
            "partial init",
            "struct defaults"
          ],
          "blocks": [
            {
              "kind": "code",
              "title": "Declare now, initialize before use",
              "code": "var buf: [64]u8 = undefined; // declared, not initialized\n@memset(&buf, 0); // MUST write before reading\n\nconst x: u32 = undefined;\n_ = x + 1; // UB: reads undefined memory (0xAA fill in Debug)"
            },
            {
              "kind": "warn",
              "content": "Reading `undefined` is **UB in every mode**. Debug fills with `0xAA` so the damage shows up as crashes or absurd values instead of silent luck."
            },
            {
              "kind": "list",
              "items": [
                "Defer-init pattern: `var v: T = undefined;` then assign **all** fields before first read",
                "Struct literals: initialize every field or give fields defaults — no partial init",
                "`@memset(ptr, 0)` bulk-initializes a buffer",
                "Legit uses: buffers a function will fully overwrite, or values the OS/writer fills before you read"
              ]
            }
          ]
        },
        {
          "id": "got-pointers",
          "title": "Pointer Pitfalls",
          "summary": "Dangling after growth, misaligned casts, and the four pointer spellings people mix up.",
          "keywords": [
            "dangling",
            "invalidation",
            "alignment",
            "aligncast",
            "slice vs pointer",
            "capture",
            "use after free"
          ],
          "blocks": [
            {
              "kind": "code",
              "title": "Invalidation after growth",
              "code": "var list: std.ArrayList(u32) = .empty;\ndefer list.deinit(gpa);\ntry list.append(gpa, 1);\nconst first: *u32 = &list.items[0];\ntry list.append(gpa, 2); // may reallocate items\n// 'first' may now DANGLE - retake &list.items[0]"
            },
            {
              "kind": "list",
              "items": [
                "Alignment: casting to a stricter alignment via `@ptrCast` needs `@alignCast` — misaligned access is UB",
                "Use-after-free: reading after `free` (see **Lifetime Rules**)",
                "`[]T` (len known) vs `[*]T` (many, unknown len) vs `[*c]T` (C) vs `*T` (exactly one) — coerce deliberately",
                "Capture semantics: `for (items) |item|` gives a **copy**; `|*item|` gives `*T` so writes stick"
              ]
            },
            {
              "kind": "code",
              "title": "Copy vs by-reference capture",
              "code": "for (items) |item| {} // item is a copy - writes are lost\nfor (items) |*item| { // item: *T - mutations persist\n    item.* += 1;\n}"
            }
          ]
        },
        {
          "id": "got-lang",
          "title": "Language Surprises",
          "summary": "Small rules that trip up newcomers: comptime_int locals, bool-only conditions, mandatory else, no shadowing.",
          "keywords": [
            "surprises",
            "comptime_int",
            "usize",
            "shadowing",
            "unused",
            "discard",
            "else",
            "switch",
            "increment"
          ],
          "blocks": [
            {
              "kind": "list",
              "items": [
                "`!` negates a bool; `~` is bitwise-not for integers — there is no `~` for bools",
                "No `x++` / `x--`: write `x += 1`",
                "`if` conditions must be `bool` — no truthy integers or pointers",
                "`var x = 1;` is a **compile error** at runtime scope: the literal is `comptime_int`, so annotate the type",
                "Indexing takes `usize` — cast with `@intCast` (or `@as`) first",
                "Shadowing is forbidden: an inner `x` cannot hide an outer `x`",
                "Unused variables, parameters and errors are compile errors — discard with `_ = x;`",
                "Blocks are expressions: an `if` used as a value **requires** `else`",
                "`switch` must be exhaustive (add `else =>` to cover the rest)"
              ]
            },
            {
              "kind": "code",
              "title": "comptime_int strikes",
              "code": "var x = 1; // ERROR: '1' is comptime_int, runtime vars need a type\nvar y: i32 = 1; // OK"
            },
            {
              "kind": "code",
              "title": "Value-context if requires else",
              "code": "const maybe: ?u32 = null;\nconst v = if (maybe) |n| n else 0; // else is mandatory here\nconst s = switch (maybe) { .some => 1, .none => 0 }; // exhaustive too"
            }
          ]
        },
        {
          "id": "got-versions",
          "title": "Version Migration Traps (0.15 → 0.16)",
          "summary": "The rename-and-remove list that will break older code: containers, mem helpers, IO, @cImport, @Type.",
          "since": "0.16",
          "keywords": [
            "migration",
            "0.15",
            "0.16",
            "renamed",
            "removed",
            "unmanaged",
            "find",
            "trimstart",
            "cimport"
          ],
          "blocks": [
            {
              "kind": "table",
              "headers": [
                "0.15 and earlier",
                "0.16"
              ],
              "rows": [
                [
                  "`var l = ArrayList(T).init(gpa)`",
                  "`var l: ArrayList(T) = .empty` + allocator per call"
                ],
                [
                  "`std.heap.GeneralPurposeAllocator`",
                  "`std.heap.DebugAllocator`"
                ],
                [
                  "`std.mem.trimLeft` / `trimRight`",
                  "`trimStart` / `trimEnd`"
                ],
                [
                  "`std.mem.indexOf*`",
                  "`std.mem.find*` (`indexOf` → `find`, `lastIndexOf` → `findLast`)"
                ],
                [
                  "`@cImport({ @cInclude(...) })`",
                  "`b.addTranslateC` + module import"
                ],
                [
                  "`@Type(...)`",
                  "`@Int`, `@Float`, `@Array`, `@Pointer`, ... type builders"
                ],
                [
                  "`std.process.argsAlloc()`",
                  "`init.minimal.args.toSlice(init.arena.allocator())`"
                ],
                [
                  "`std.fs.cwd()` / `std.fs`",
                  "`std.Io.Dir` / `std.Io.File` with an `io` handle"
                ],
                [
                  "`aw.writer()` method call",
                  "`aw.writer` **field** access"
                ],
                [
                  "`std.Thread.Pool`",
                  "removed — use `std.Io`"
                ],
                [
                  "managed `ArrayHashMap`",
                  "`std.array_hash_map.Auto` / `.String` / `.Custom`"
                ],
                [
                  "global args / env",
                  "`main(init)` only — `init.minimal.args`, `init.environ_map`"
                ]
              ]
            },
            {
              "kind": "warn",
              "content": "`Writer.Allocating.writer` is a **field**, not a method — `aw.writer()` is a compile error; write `aw.writer.print(...)`. Full details: the 0.16.0 release notes."
            }
          ]
        },
        {
          "id": "got-zen",
          "title": "The Zen of Zig",
          "summary": "Six lines that explain most design decisions — and most of the gotchas above.",
          "keywords": [
            "zen",
            "philosophy",
            "intent",
            "one way",
            "hidden control flow",
            "hidden allocations"
          ],
          "blocks": [
            {
              "kind": "list",
              "ordered": false,
              "items": [
                "Communicate intent precisely.",
                "Edge cases matter.",
                "Only one way to do things.",
                "No hidden control flow.",
                "No hidden allocations.",
                "Together, we serve the users."
              ]
            },
            {
              "kind": "text",
              "content": "The design enforces it: allocation is explicit (the `Allocator` parameter), control flow is visible (errors are values, no exceptions, no operator overloading) — which is exactly why the gotchas in this section exist."
            }
          ]
        },
        {
          "id": "got-errors-read",
          "title": "Reading Compiler Errors",
          "summary": "Zig errors explain coercions, offer `help:` hints, and can be produced without emitting a binary.",
          "keywords": [
            "error messages",
            "help",
            "coercion",
            "fno-emit-bin",
            "summary all",
            "unused",
            "expected type"
          ],
          "blocks": [
            {
              "kind": "list",
              "items": [
                "Errors include **coercion explanations** (\"expected type X, found Y\") — read top to bottom",
                "`help:` lines usually contain the fix",
                "**Reference traces** show where each mismatched type came from",
                "Typecheck only: `zig build-exe main.zig -fno-emit-bin`",
                "Step-level detail for build failures: `zig build --summary all`",
                "`error: unused ...` → discard with `_ = x;` (or name it `_`)",
                "\"expected type\" mismatches → check the casting table (see **Quick Reference**)"
              ]
            },
            {
              "kind": "code",
              "title": "The discard fix",
              "code": "fn f(gpa: std.mem.Allocator) !void {\n    const s = try gpa.alloc(u8, 8); // error: unused local variable 's'\n    _ = s; // fix: explicit discard\n}"
            },
            {
              "kind": "text",
              "content": "Zig's error messages are unusually pedagogical — when the compiler complains, the explanation is usually longer than the fix."
            }
          ]
        }
      ]
    },
    {
      "id": "reference-tables",
      "title": "Quick Reference",
      "icon": "Table",
      "blurb": "Print-and-pin tables: operators, literals, builtins, casts, and where to read more.",
      "entries": [
        {
          "id": "ref-operators",
          "title": "Operators & Precedence",
          "summary": "All operators, grouped highest to lowest precedence, with the wrap/saturate variants.",
          "keywords": [
            "operators",
            "precedence",
            "concat",
            "wrapping",
            "saturating",
            "assignment",
            "bitwise"
          ],
          "blocks": [
            {
              "kind": "table",
              "headers": [
                "precedence",
                "operators",
                "meaning"
              ],
              "rows": [
                [
                  "1 (highest)",
                  "`x.*  x.?  x[i]  x.f  f()  @b()`",
                  "postfix: deref, unwrap, index, field, call"
                ],
                [
                  "2",
                  "`!  -  ~  &x`",
                  "unary: not, negate, bit-not, address-of"
                ],
                [
                  "3",
                  "`*  /  %  **  *%  *|`",
                  "multiply, divide, remainder, power, wrap-mul, sat-mul"
                ],
                [
                  "4",
                  "`+  -  ++  +|  +%  -|  -%`",
                  "add, subtract, **concat**, sat-add, wrap-add, sat-sub, wrap-sub"
                ],
                [
                  "5",
                  "`<<  >>  <<|`",
                  "shift left/right, saturating shift left"
                ],
                [
                  "6",
                  "`&  ^  |`",
                  "bitwise and, xor, or"
                ],
                [
                  "7",
                  "`==  !=  <  >  <=  >=`",
                  "comparison"
                ],
                [
                  "8",
                  "`and  or`",
                  "logical, short-circuit"
                ],
                [
                  "9 (lowest)",
                  "`=  +=  -=  *=  /=  %=  *=  **=  *%=  <<=  >>=  &=  |=  ^=  +|=  +%=  -|=  -%=  <<|=`",
                  "assignment (a statement, not an expression)"
                ]
              ]
            },
            {
              "kind": "list",
              "items": [
                "`++` concatenates arrays and slices (comptime-known result for arrays)",
                "`+%` / `-%` / `*%` wrap on overflow; `+|` / `-|` / `*|` saturate; `<<|` saturates the shift",
                "`and` / `or` short-circuit and require bool operands",
                "No operator overloading anywhere — operators always mean bit/math operations"
              ]
            }
          ]
        },
        {
          "id": "ref-literals",
          "title": "Literal Syntax",
          "summary": "Every literal form: integer bases, chars, floats, strings, enum/struct/error literals, ranges.",
          "keywords": [
            "literal",
            "hex",
            "binary",
            "multiline string",
            "enum literal",
            "struct literal",
            "tuple",
            "anonymous list",
            "range"
          ],
          "blocks": [
            {
              "kind": "table",
              "headers": [
                "kind",
                "examples"
              ],
              "rows": [
                [
                  "integers",
                  "`42`  `0x2A`  `0o52`  `0b101010`  `1_000_000` (separators)"
                ],
                [
                  "characters",
                  "`'a'`  `'\\n'`  `'\\x1b'`  `'\\u{263A}'`"
                ],
                [
                  "floats",
                  "`3.14`  `1e9`  `0x1.8p3` (hex float)"
                ],
                [
                  "strings",
                  "`\"hi\\n\"`  `\"\\xAF\"` (byte)  multiline with `\\\\`"
                ],
                [
                  "enum literal",
                  "`.tag`"
                ],
                [
                  "struct / tuple literal",
                  "`.{ .x = 1 }`  `.{ 1, 2 }`"
                ],
                [
                  "errors",
                  "`error.OutOfMemory`  `error.Set{ .A, .B }`"
                ],
                [
                  "switch ranges",
                  "`'a'...'z'`  `1...9` (three dots)"
                ],
                [
                  "anon list → array",
                  "`.{ 1, 2, 3 }` coerces to `[3]u8` (anonymous list, since 0.15)"
                ]
              ]
            },
            {
              "kind": "code",
              "title": "Literals in one glance",
              "code": "const hex: u32 = 0xFF_00;\nconst nl: u8 = '\\n';\nconst f: f64 = 0x1.8p3; // 12.0\nconst s: []const u8 = \"hi\\n\" ++ \"there\";\nconst multi =\n    \\\\line one\n    \\\\line two\n;"
            },
            {
              "kind": "text",
              "content": "`.{ ... }` is the anonymous literal — it becomes a struct (`.{ .x = 1 }`), a tuple (`.{ 1, 2 }`), or an array/tuple depending on the target type."
            }
          ]
        },
        {
          "id": "ref-builtins",
          "title": "Builtin Functions Index",
          "summary": "The `@`-functions grouped by purpose — introspection, casts, math, SIMD, compile-time, runtime.",
          "keywords": [
            "builtins",
            "typeinfo",
            "intcast",
            "bitcast",
            "splat",
            "compileerror",
            "embedfile",
            "panic",
            "type constructors"
          ],
          "blocks": [
            {
              "kind": "table",
              "headers": [
                "group",
                "builtins"
              ],
              "rows": [
                [
                  "Introspection",
                  "`@typeInfo @typeName @TypeOf @sizeOf @alignOf @bitSizeOf @offsetOf @This @field @hasDecl @hasField @extern`"
                ],
                [
                  "Casts",
                  "`@intCast @truncate @floatCast @floatFromInt @intFromFloat @enumFromInt @intFromEnum @bitCast @ptrCast @alignCast @constCast @volatileCast @addrSpaceCast`"
                ],
                [
                  "Math",
                  "`@min @max @abs @sqrt @sin @cos @tan @exp @log @log2 @log10 @floor @ceil @round @trunc @mod @rem @divTrunc @divFloor @divExact @mulAdd @popCount @clz @ctz @byteSwap @bitReverse`"
                ],
                [
                  "Overflow-aware",
                  "`@addWithOverflow @subWithOverflow @mulWithOverflow @shlWithOverflow`"
                ],
                [
                  "SIMD",
                  "`@splat @reduce @select @shuffle @mulAdd`"
                ],
                [
                  "Compile-time",
                  "`@compileError @compileLog @setEvalBranchQuota @setRuntimeSafety @embedFile @import @src @call @export`"
                ],
                [
                  "Runtime",
                  "`@panic @breakpoint @fence @errorName @errorReturnTrace @returnAddress @frameAddress @prefetch`"
                ],
                [
                  "Pointers",
                  "`@ptrFromInt @intFromPtr`"
                ],
                [
                  "Type creators (0.16)",
                  "`@Int @Float @Array @Pointer @Struct @Union @Enum @Tuple @Opaque @Optional @ErrorUnion @ErrorSet @Fn @EnumLiteral`"
                ]
              ]
            },
            {
              "kind": "text",
              "content": "`@Type` was removed in **0.16** and replaced by the individual type creators in the last row. Full signatures live in the std docs; every builtin is documented in the language reference."
            }
          ]
        },
        {
          "id": "ref-casting",
          "title": "Casting Cheat Table",
          "summary": "Which builtin converts what — and where the safe-mode checks kick in.",
          "keywords": [
            "cast",
            "intcast",
            "floatcast",
            "intfromfloat",
            "enumfromint",
            "ptrcast",
            "bitcast",
            "coercion"
          ],
          "blocks": [
            {
              "kind": "table",
              "headers": [
                "from → to",
                "builtin",
                "notes"
              ],
              "rows": [
                [
                  "int → smaller int",
                  "`@intCast`",
                  "panics on truncation in safe modes"
                ],
                [
                  "int → bigger int",
                  "coercion or `@intCast`",
                  "implicit up-cast just works"
                ],
                [
                  "int → float",
                  "`@floatFromInt`",
                  "—"
                ],
                [
                  "float → int",
                  "`@intFromFloat`",
                  "truncates; out-of-range is UB"
                ],
                [
                  "float → smaller float",
                  "`@floatCast`",
                  "may lose precision"
                ],
                [
                  "enum → int",
                  "`@intFromEnum`",
                  "—"
                ],
                [
                  "int → enum",
                  "`@enumFromInt`",
                  "no validation — can create invalid tags"
                ],
                [
                  "ptr → ptr",
                  "`@ptrCast` (+ `@alignCast`)",
                  "keep the alignment legal"
                ],
                [
                  "bits reinterpret",
                  "`@bitCast`",
                  "source and target must have equal size"
                ],
                [
                  "`[]T` → `[]const T`",
                  "coercion",
                  "implicit, free"
                ],
                [
                  "`?T` → `T`",
                  "`.?`",
                  "panics on null in safe modes"
                ],
                [
                  "`E!T` → `T`",
                  "`try` / `catch`",
                  "error handling, not a cast builtin"
                ]
              ]
            },
            {
              "kind": "code",
              "title": "The big four",
              "code": "const big: u64 = 300;\nconst small: i32 = @intCast(big); // panics if it doesn't fit (safe modes)\nconst approx: f64 = @floatFromInt(big);\nconst bits: u32 = @bitCast(@as(f32, 1.0)); // reinterpret same-size bits"
            },
            {
              "kind": "text",
              "content": "When two types coerce implicitly, no builtin is needed — the \"expected type X, found Y\" error usually tells you which cast the compiler wants."
            }
          ]
        },
        {
          "id": "ref-resources",
          "title": "Links & Resources",
          "summary": "Official docs, std reference, ziglings, community — and where the 0.16 changes are listed.",
          "keywords": [
            "resources",
            "links",
            "documentation",
            "ziglings",
            "ziggit",
            "release notes",
            "cheats.rs"
          ],
          "blocks": [
            {
              "kind": "table",
              "headers": [
                "resource",
                "what it is"
              ],
              "rows": [
                [
                  "ziglang.org",
                  "downloads, official docs, blog"
                ],
                [
                  "ziglang.org/documentation/master/",
                  "language reference"
                ],
                [
                  "ziglang.org/documentation/master/std/",
                  "standard library docs"
                ],
                [
                  "zig.guide",
                  "community beginner guide"
                ],
                [
                  "codeberg.org/ziglings/exercises",
                  "ziglings — fix small broken programs"
                ],
                [
                  "ziggit.dev",
                  "community forum"
                ],
                [
                  "github.com/ziglang/zig",
                  "source, issues, PRs"
                ],
                [
                  "ziglang.org/download/0.16.0/release-notes.html",
                  "0.16.0 release notes — everything that changed"
                ],
                [
                  "cheats.rs",
                  "the Rust cheatsheet that inspired this site"
                ]
              ]
            },
            {
              "kind": "text",
              "content": "This site targets Zig **0.16.0**; `since` badges mark entries whose API arrived in 0.15 or 0.16. Ziglings exercise numbers appear throughout as cross-references — work through them top to bottom for muscle memory."
            }
          ]
        }
      ]
    }
  ]
}
