···11+# Project Rules
22+33+## Workflow
44+55+- **TDD mandatory**: every change requires failing test first, then passing code, then refactor.
66+- **Commit freely**: commit anytime without asking.
77+- **Push requires approval**: never push. always ask first.
88+- **Coverage**: 100% line coverage via kcov. no exceptions.
99+1010+## Verification Steps
1111+1212+1. Write failing test.
1313+2. Make test pass with minimal code.
1414+3. Refactor.
1515+4. Run `./taskfile test` โ all pass.
1616+5. Run `./taskfile coverage` โ 100.00%.
1717+6. Commit.
1818+1919+## Technical Constraints
2020+2121+- Zig 0.16.0+
2222+- Dependency injection via `Deps` struct.
2323+- No hardcoded stdout/stdin.
2424+- `use_llvm = true` on test binary for kcov.
2525+2626+## Questions?
2727+2828+Ask before uncertain decisions.
2929+3030+## Style Guide
3131+3232+See [`docs/TIGER_STYLE.md`](docs/TIGER_STYLE.md) for TigerBeetle coding style guidelines.
+21
LICENSE
···11+MIT License
22+33+Copyright (c) 2024 ZIX
44+55+Permission is hereby granted, free of charge, to any person obtaining a copy
66+of this software and associated documentation files (the "Software"), to deal
77+in the Software without restriction, including without limitation the rights
88+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
99+copies of the Software, and to permit persons to whom the Software is
1010+furnished to do so, subject to the following conditions:
1111+1212+The above copyright notice and this permission notice shall be included in all
1313+copies or substantial portions of the Software.
1414+1515+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1616+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1717+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
1818+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
1919+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
2020+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
2121+SOFTWARE.
+126
README.md
···11+> :warning: **MIRROR**: this project is a mirror from https://codefloe.com/badwatt/zix
22+33+# ZIX
44+55+
66+77+CLI tool for managing NixOS configuration.
88+99+## Requirements
1010+1111+- **Zig 0.16.0** or later
1212+- **Nix** (for nix-shell, kcov)
1313+1414+## Installation
1515+1616+```sh
1717+git clone https://github.com/alvaro17f/zix.git
1818+cd zix
1919+zig build run
2020+```
2121+2222+Move binary to PATH:
2323+2424+```sh
2525+sudo mv zig-out/bin/zix <PATH>
2626+```
2727+2828+### NixOS
2929+3030+```sh
3131+nix run github:alvaro17f/zix#target.x86_64-linux-musl
3232+```
3333+3434+Add to flake:
3535+3636+```nix
3737+{
3838+ inputs = {
3939+ zix.url = "github:alvaro17f/zix";
4040+ };
4141+}
4242+```
4343+4444+```nix
4545+{ inputs, pkgs, ... }:
4646+{
4747+ home.packages = [
4848+ inputs.zix.packages.${pkgs.system}.default
4949+ ];
5050+}
5151+```
5252+5353+## Build Commands
5454+5555+All tasks via `zig build`:
5656+5757+| Command | Description |
5858+|---|---|
5959+| `zig build` | Compile project |
6060+| `zig build run` | Build and run |
6161+| `zig build test --summary all` | Run test suite |
6262+| `zig build coverage` | Run tests under kcov, print line coverage |
6363+| `zig build docs` | Generate autodoc HTML |
6464+| `zig build docs:serve` | Build docs + serve at `localhost:8000` |
6565+| `zig build fmt` | Not available โ use `zig fmt src/` directly |
6666+6767+## Coverage
6868+6969+100% line coverage via **kcov**:
7070+7171+```sh
7272+zig build coverage
7373+# 100.0% (423/423 lines)
7474+```
7575+7676+Tests run with LLVM backend (`.use_llvm = true`) for accurate DWARF instrumentation.
7777+7878+## Documentation
7979+8080+```sh
8181+zig build docs # generate to zig-out/docs/
8282+zig build docs:serve # build + serve at http://localhost:8000
8383+```
8484+8585+Autodoc uses `///` (declarations) and `//!` (module-level) comments. No doc comments inside function bodies โ use `//` there.
8686+8787+## Usage
8888+8989+```
9090+ ***************************************************
9191+ ZIX - A simple CLI tool to update your nixos system
9292+ ***************************************************
9393+ -r : set repo path (default is $HOME/.dotfiles)
9494+ -n : set hostname (default is OS hostname)
9595+ -k : set generations to keep (default is 10)
9696+ -u : set update to true (default is false)
9797+ -d : set diff to true (default is false)
9898+ -h, help : Display this help message
9999+ -v, version : Display the current version
100100+```
101101+102102+## Project Structure
103103+104104+```
105105+build.zig Build configuration
106106+build/coverage.zig kcov JSON parser (Zig)
107107+build/serve.zig Static HTTP server for autodoc (Zig)
108108+src/main.zig Entry point, allocator setup
109109+src/app/init.zig CLI flag parsing, app dispatch
110110+src/app/cli.zig Command building, execution pipeline
111111+src/app/config.zig Config struct with defaults + validation
112112+src/core/commands.zig Shell command string builders
113113+src/core/io.zig Formatted output + ANSI style constants
114114+src/core/process.zig Shell process runner
115115+src/core/ui.zig Terminal UI: titles, help, prompts
116116+src/core/static_allocator.zig Two-phase allocator (init โ static โ deinit)
117117+```
118118+119119+## License
120120+121121+MIT. See LICENSE.
122122+123123+## Style Guide
124124+125125+This project follows [TigerBeetle's TIGER_STYLE](docs/TIGER_STYLE.md).
126126+
···11+//! Coverage report parser for kcov JSON output.
22+//!
33+//! Walks the coverage directory, finds coverage.json, and prints
44+//! a summary line: `{percent}% ({covered}/{total} lines)`.
55+//! Designed to be built and run as part of `zig build coverage`.
66+77+const std = @import("std");
88+99+/// Entry point. Reads kcov output directory, parses coverage.json,
1010+/// and prints percentage summary. Exits 1 if no data found.
1111+pub fn main(init: std.process.Init) !void {
1212+ const allocator = init.gpa;
1313+ const io = init.io;
1414+ const args = init.minimal.args;
1515+1616+ if (args.vector.len < 2) {
1717+ std.debug.print("usage: coverage-report <coverage-dir>\n", .{});
1818+ std.process.exit(1);
1919+ }
2020+ const coverage_dir = std.mem.sliceTo(args.vector[1], 0);
2121+2222+ const cwd = std.Io.Dir.cwd();
2323+2424+ const dir = cwd.openDir(io, coverage_dir, .{ .iterate = true }) catch |err| switch (err) {
2525+ error.FileNotFound => {
2626+ std.debug.print("No coverage data found\n", .{});
2727+ std.process.exit(1);
2828+ },
2929+ else => return err,
3030+ };
3131+3232+ var walker = try dir.walk(allocator);
3333+ defer walker.deinit();
3434+3535+ while (try walker.next(io)) |entry| {
3636+ if (entry.kind != .file) continue;
3737+ if (!std.mem.endsWith(u8, entry.basename, "coverage.json")) continue;
3838+3939+ const path = try std.fmt.allocPrint(allocator, "{s}/{s}", .{ coverage_dir, entry.path });
4040+ defer allocator.free(path);
4141+4242+ const contents = cwd.readFileAlloc(io, path, allocator, .limited(1 << 20)) catch continue;
4343+ defer allocator.free(contents);
4444+4545+ const parsed = std.json.parseFromSlice(std.json.Value, allocator, contents, .{}) catch continue;
4646+ defer parsed.deinit();
4747+4848+ const root = parsed.value.object;
4949+ const pct_str = root.get("percent_covered").?.string;
5050+ const pct = std.fmt.parseFloat(f64, pct_str) catch 0.0;
5151+ const covered = root.get("covered_lines").?.integer;
5252+ const total = root.get("total_lines").?.integer;
5353+5454+ std.debug.print("{d:.1}% ({d}/{d} lines)\n", .{ pct, covered, total });
5555+ return;
5656+ }
5757+5858+ std.debug.print("No coverage data found\n", .{});
5959+ std.process.exit(1);
6060+}
+123
build/serve.zig
···11+//! Static HTTP file server for Zig autodoc.
22+//!
33+//! Serves files from a directory over HTTP/1.1 with `Connection: close`.
44+//! Supports WASM, JS, HTML, JSON, CSS, SVG, and TAR content types.
55+//! Designed to be built and run as part of `zig build docs:serve`.
66+77+const std = @import("std");
88+const Io = std.Io;
99+1010+/// Listens on port 8000 and serves files from the directory given as
1111+/// the first argument. Defaults to current directory if no argument.
1212+pub fn main(init: std.process.Init) !void {
1313+ const io = init.io;
1414+ const args = init.minimal.args;
1515+1616+ const port: u16 = 8000;
1717+ const serve_dir = if (args.vector.len > 1) std.mem.sliceTo(args.vector[1], 0) else ".";
1818+1919+ const address = Io.net.IpAddress.parseIp4("0.0.0.0", port) catch {
2020+ std.debug.print("invalid port\n", .{});
2121+ std.process.exit(1);
2222+ };
2323+ var server = Io.net.IpAddress.listen(&address, io, .{}) catch |err| {
2424+ std.debug.print("failed to listen on port {d}: {s}\n", .{ port, @errorName(err) });
2525+ std.process.exit(1);
2626+ };
2727+2828+ std.debug.print("serving {s} at http://localhost:{d}\n", .{ serve_dir, port });
2929+3030+ while (true) {
3131+ const stream = server.accept(io) catch continue;
3232+ handleClient(io, stream, serve_dir) catch continue;
3333+ }
3434+}
3535+3636+/// Reads one HTTP request, resolves the path to a file in `serve_dir`,
3737+/// and writes the response with appropriate `Content-Type` and
3838+/// `Connection: close` headers.
3939+fn handleClient(io: Io, stream: Io.net.Stream, serve_dir: []const u8) !void {
4040+ var rbuf: [8192]u8 = undefined;
4141+ var net_reader = stream.reader(io, &rbuf);
4242+ var reader = &net_reader.interface;
4343+4444+ // Read request headers
4545+ while (true) {
4646+ reader.fillMore() catch break;
4747+ const data = reader.buffer[0..reader.end];
4848+ if (std.mem.indexOf(u8, data, "\r\n\r\n")) |_| break;
4949+ }
5050+5151+ const request = reader.buffer[reader.seek..reader.end];
5252+5353+ // Parse path from request line: "GET /path HTTP/1.1"
5454+ var path_start: usize = 0;
5555+ var path_end: usize = 0;
5656+ for (request, 0..) |c, i| {
5757+ if (c == ' ' and path_start == 0 and i >= 3) {
5858+ path_start = i + 1;
5959+ } else if (c == ' ' and path_start > 0 and path_end == 0) {
6060+ path_end = i;
6161+ break;
6262+ }
6363+ }
6464+ if (path_start == 0 or path_end == 0) return;
6565+ const raw_path = request[path_start..path_end];
6666+6767+ // Default to index.html for directory paths
6868+ const path = if (std.mem.endsWith(u8, raw_path, "/") or std.mem.endsWith(u8, raw_path, "/.."))
6969+ "/index.html"
7070+ else
7171+ raw_path;
7272+7373+ // Block path traversal
7474+ if (std.mem.containsAtLeast(u8, path, 1, "../")) {
7575+ respond(io, stream, "HTTP/1.1 403 Forbidden\r\nConnection: close\r\nContent-Length: 15\r\n\r\n403 Forbidden");
7676+ return;
7777+ }
7878+7979+ const file_path = path[1..]; // strip leading /
8080+8181+ var path_buf: [4096]u8 = undefined;
8282+ const full_path = std.fmt.bufPrint(&path_buf, "{s}/{s}", .{ serve_dir, file_path }) catch return;
8383+8484+ const content_type = ct: {
8585+ if (std.mem.endsWith(u8, file_path, ".html")) break :ct "text/html";
8686+ if (std.mem.endsWith(u8, file_path, ".js")) break :ct "application/javascript";
8787+ if (std.mem.endsWith(u8, file_path, ".wasm")) break :ct "application/wasm";
8888+ if (std.mem.endsWith(u8, file_path, ".css")) break :ct "text/css";
8989+ if (std.mem.endsWith(u8, file_path, ".json")) break :ct "application/json";
9090+ if (std.mem.endsWith(u8, file_path, ".svg")) break :ct "image/svg+xml";
9191+ if (std.mem.endsWith(u8, file_path, ".tar")) break :ct "application/x-tar";
9292+ break :ct "application/octet-stream";
9393+ };
9494+9595+ var gpa_buf: [1 << 25]u8 = undefined;
9696+ var gpa_fixed = std.heap.FixedBufferAllocator.init(&gpa_buf);
9797+ const allocator = gpa_fixed.allocator();
9898+9999+ const cwd = Io.Dir.cwd();
100100+ const contents = cwd.readFileAlloc(io, full_path, allocator, .limited(1 << 25)) catch {
101101+ respond(io, stream, "HTTP/1.1 404 Not Found\r\nConnection: close\r\nContent-Length: 13\r\n\r\n404 Not Found");
102102+ return;
103103+ };
104104+105105+ var wbuf: [4096]u8 = undefined;
106106+ var net_writer = stream.writer(io, &wbuf);
107107+ var writer = &net_writer.interface;
108108+ writer.print(
109109+ "HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Length: {d}\r\nContent-Type: {s}\r\n\r\n",
110110+ .{ contents.len, content_type },
111111+ ) catch return;
112112+ writer.writeAll(contents) catch return;
113113+ writer.flush() catch return;
114114+}
115115+116116+/// Sends a complete HTTP response in one write.
117117+fn respond(io: Io, stream: Io.net.Stream, msg: []const u8) void {
118118+ var wbuf: [512]u8 = undefined;
119119+ var net_writer = stream.writer(io, &wbuf);
120120+ var writer = &net_writer.interface;
121121+ writer.writeAll(msg) catch return;
122122+ writer.flush() catch return;
123123+}
+511
docs/TIGER_STYLE.md
···11+# TigerStyle
22+33+## The Essence Of Style
44+55+> โThere are three things extremely hard: steel, a diamond, and to know one's self.โ โ Benjamin
66+> Franklin
77+88+TigerBeetle's coding style is evolving. A collective give-and-take at the intersection of
99+engineering and art. Numbers and human intuition. Reason and experience. First principles and
1010+knowledge. Precision and poetry. Just like music. A tight beat. A rare groove. Words that rhyme and
1111+rhymes that break. Biodigital jazz. This is what we've learned along the way. The best is yet to
1212+come.
1313+1414+## Why Have Style?
1515+1616+Another word for style is design.
1717+1818+> โThe design is not just what it looks like and feels like. The design is how it works.โ โ Steve
1919+> Jobs
2020+2121+Our design goals are safety, performance, and developer experience. In that order. All three are
2222+important. Good style advances these goals. Does the code make for more or less safety, performance
2323+or developer experience? That is why we need style.
2424+2525+Put this way, style is more than readability, and readability is table stakes, a means to an end
2626+rather than an end in itself.
2727+2828+> โ...in programming, style is not something to pursue directly. Style is necessary only where
2929+> understanding is missing.โ โ [Let Over
3030+> Lambda](https://letoverlambda.com/index.cl/guest/chap1.html)
3131+3232+This document explores how we apply these design goals to coding style. First, a word on simplicity,
3333+elegance and technical debt.
3434+3535+## On Simplicity And Elegance
3636+3737+Simplicity is not a free pass. It's not in conflict with our design goals. It need not be a
3838+concession or a compromise.
3939+4040+Rather, simplicity is how we bring our design goals together, how we identify the โsuper ideaโ that
4141+solves the axes simultaneously, to achieve something elegant.
4242+4343+> โSimplicity and elegance are unpopular because they require hard work and discipline to achieveโ โ
4444+> Edsger Dijkstra
4545+4646+Contrary to popular belief, simplicity is also not the first attempt but the hardest revision. It's
4747+easy to say โlet's do something simpleโ, but to do that in practice takes thought, multiple passes,
4848+many sketches, and still we may have to [โthrow one
4949+awayโ](https://en.wikipedia.org/wiki/The_Mythical_Man-Month).
5050+5151+The hardest part, then, is how much thought goes into everything.
5252+5353+We spend this mental energy upfront, proactively rather than reactively, because we know that when
5454+the thinking is done, what is spent on the design will be dwarfed by the implementation and testing,
5555+and then again by the costs of operation and maintenance.
5656+5757+An hour or day of design is worth weeks or months in production:
5858+5959+> โthe simple and elegant systems tend to be easier and faster to design and get right, more
6060+> efficient in execution, and much more reliableโ โ Edsger Dijkstra
6161+6262+## Technical Debt
6363+6464+What could go wrong? What's wrong? Which question would we rather ask? The former, because code,
6565+like steel, is less expensive to change while it's hot. A problem solved in production is many times
6666+more expensive than a problem solved in implementation, or a problem solved in design.
6767+6868+Since it's hard enough to discover showstoppers, when we do find them, we solve them. We don't allow
6969+potential memcpy latency spikes, or exponential complexity algorithms to slip through.
7070+7171+> โYou shall not pass!โ โ Gandalf
7272+7373+In other words, TigerBeetle has a โzero technical debtโ policy. We do it right the first time. This
7474+is important because the second time may not transpire, and because doing good work, that we can be
7575+proud of, builds momentum.
7676+7777+We know that what we ship is solid. We may lack crucial features, but what we have meets our design
7878+goals. This is the only way to make steady incremental progress, knowing that the progress we have
7979+made is indeed progress.
8080+8181+## Safety
8282+8383+> โThe rules act like the seat-belt in your car: initially they are perhaps a little uncomfortable,
8484+> but after a while their use becomes second-nature and not using them becomes unimaginable.โ โ
8585+> Gerard J. Holzmann
8686+8787+[NASA's Power of Ten โ Rules for Developing Safety Critical
8888+Code](https://spinroot.com/gerard/pdf/P10.pdf) will change the way you code forever. To expand:
8989+9090+- Use **only very simple, explicit control flow** for clarity. **Do not use recursion** to ensure
9191+ that all executions that should be bounded are bounded. Use **only a minimum of excellent
9292+ abstractions** but only if they make the best sense of the domain. Abstractions are [never zero
9393+ cost](https://isaacfreund.com/blog/2022-05/). Every abstraction introduces the risk of a leaky
9494+ abstraction.
9595+9696+- **Put a limit on everything** because, in reality, this is what we expectโeverything has a limit.
9797+ For example, all loops and all queues must have a fixed upper bound to prevent infinite loops or
9898+ tail latency spikes. This follows the [โfail-fastโ](https://en.wikipedia.org/wiki/Fail-fast)
9999+ principle so that violations are detected sooner rather than later. Where a loop cannot terminate
100100+ (e.g. an event loop), this must be asserted.
101101+102102+- Use explicitly-sized types like `u32` for everything, avoid architecture-specific `usize`.
103103+104104+- **Assertions detect programmer errors. Unlike operating errors, which are expected and which must
105105+ be handled, assertion failures are unexpected. The only correct way to handle corrupt code is to
106106+ crash. Assertions downgrade catastrophic correctness bugs into liveness bugs. Assertions are a
107107+ force multiplier for discovering bugs by fuzzing.**
108108+109109+ - **Assert all function arguments and return values, pre/postconditions and invariants.** A
110110+ function must not operate blindly on data it has not checked. The purpose of a function is to
111111+ increase the probability that a program is correct. Assertions within a function are part of how
112112+ functions serve this purpose. The assertion density of the code must average a minimum of two
113113+ assertions per function.
114114+115115+ - **[Pair assertions](https://tigerbeetle.com/blog/2023-12-27-it-takes-two-to-contract).** For
116116+ every property you want to enforce, try to find at least two different code paths where an
117117+ assertion can be added. For example, assert validity of data right before writing it to disk,
118118+ and also immediately after reading from disk.
119119+120120+ - On occasion, you may use a blatantly true assertion instead of a comment as stronger
121121+ documentation where the assertion condition is critical and surprising.
122122+123123+ - Split compound assertions: prefer `assert(a); assert(b);` over `assert(a and b);`.
124124+ The former is simpler to read, and provides more precise information if the condition fails.
125125+126126+ - Use single-line `if` to assert an implication: `if (a) assert(b)`.
127127+128128+ - **Assert the relationships of compile-time constants** as a sanity check, and also to document
129129+ and enforce [subtle
130130+ invariants](https://github.com/coilhq/tigerbeetle/blob/db789acfb93584e5cb9f331f9d6092ef90b53ea6/src/vsr/journal.zig#L45-L47)
131131+ or [type
132132+ sizes](https://github.com/coilhq/tigerbeetle/blob/578ac603326e1d3d33532701cb9285d5d2532fe7/src/ewah.zig#L41-L53).
133133+ Compile-time assertions are extremely powerful because they are able to check a program's design
134134+ integrity _before_ the program even executes.
135135+136136+ - **The golden rule of assertions is to assert the _positive space_ that you do expect AND to
137137+ assert the _negative space_ that you do not expect** because where data moves across the
138138+ valid/invalid boundary between these spaces is where interesting bugs are often found. This is
139139+ also why **tests must test exhaustively**, not only with valid data but also with invalid data,
140140+ and as valid data becomes invalid.
141141+142142+ - Assertions are a safety net, not a substitute for human understanding. With simulation testing,
143143+ there is the temptation to trust the fuzzer. But a fuzzer can prove only the presence of bugs,
144144+ not their absence. Therefore:
145145+ - Build a precise mental model of the code first,
146146+ - encode your understanding in the form of assertions,
147147+ - write the code and comments to explain and justify the mental model to your reviewer,
148148+ - and use VOPR as the final line of defense, to find bugs in your and reviewer's understanding
149149+ of code.
150150+151151+- All memory must be statically allocated at startup. **No memory may be dynamically allocated (or
152152+ freed and reallocated) after initialization.** This avoids unpredictable behavior that can
153153+ significantly affect performance, and avoids use-after-free. As a second-order effect, it is our
154154+ experience that this also makes for more efficient, simpler designs that are more performant and
155155+ easier to maintain and reason about, compared to designs that do not consider all possible memory
156156+ usage patterns upfront as part of the design.
157157+158158+- Declare variables at the **smallest possible scope**, and **minimize the number of variables in
159159+ scope**, to reduce the probability that variables are misused.
160160+161161+- There's a sharp discontinuity between a function fitting on a screen, and having to scroll to
162162+ see how long it is. For this physical reason we enforce a **hard limit of 70 lines per function**.
163163+ Art is born of constraints. There are many ways to cut a wall of code into chunks of 70 lines,
164164+ but only a few splits will feel right. Some rules of thumb:
165165+166166+ * Good function shape is often the inverse of an hourglass: a few parameters, a simple return
167167+ type, and a lot of meaty logic between the braces.
168168+ * Centralize control flow. When splitting a large function, try to keep all switch/if
169169+ statements in the "parent" function, and move non-branchy logic fragments to helper
170170+ functions. Divide responsibility. All control flow should be handled by _one_ function, the rest shouldn't
171171+ care about control flow at all. In other words,
172172+ ["push `if`s up and `for`s down"](https://matklad.github.io/2023/11/15/push-ifs-up-and-fors-down.html).
173173+ * Similarly, centralize state manipulation. Let the parent function keep all relevant state in
174174+ local variables, and use helpers to compute what needs to change, rather than applying the
175175+ change directly. Keep leaf functions pure.
176176+177177+- Appreciate, from day one, **all compiler warnings at the compiler's strictest setting**.
178178+179179+- Whenever your program has to interact with external entities, **don't do things directly in
180180+ reaction to external events**. Instead, your program should run at its own pace. Not only does
181181+ this make your program safer by keeping the control flow of your program under your control, it
182182+ also improves performance for the same reason (you get to batch, instead of context switching on
183183+ every event). Additionally, this makes it easier to maintain bounds on work done per time period.
184184+185185+Beyond these rules:
186186+187187+- Compound conditions that evaluate multiple booleans make it difficult for the reader to verify
188188+ that all cases are handled. Split compound conditions into simple conditions using nested
189189+ `if/else` branches. Split complex `else if` chains into `else { if { } }` trees. This makes the
190190+ branches and cases clear. Again, consider whether a single `if` does not also need a matching
191191+ `else` branch, to ensure that the positive and negative spaces are handled or asserted.
192192+193193+- Negations are not easy! State invariants positively. When working with lengths and indexes, this
194194+ form is easy to get right (and understand):
195195+196196+ ```zig
197197+ if (index < length) {
198198+ // The invariant holds.
199199+ } else {
200200+ // The invariant doesn't hold.
201201+ }
202202+ ```
203203+204204+ This form is harder, and also goes against the grain of how `index` would typically be compared to
205205+ `length`, for example, in a loop condition:
206206+207207+ ```zig
208208+ if (index >= length) {
209209+ // It's not true that the invariant holds.
210210+ }
211211+ ```
212212+213213+- All errors must be handled. An [analysis of production failures in distributed data-intensive
214214+ systems](https://www.usenix.org/system/files/conference/osdi14/osdi14-paper-yuan.pdf) found that
215215+ the majority of catastrophic failures could have been prevented by simple testing of error
216216+ handling code.
217217+218218+> โSpecifically, we found that almost all (92%) of the catastrophic system failures are the result
219219+> of incorrect handling of non-fatal errors explicitly signaled in software.โ
220220+221221+- **Always motivate, always say why**. Never forget to say why. Because if you explain the rationale
222222+ for a decision, it not only increases the hearer's understanding, and makes them more likely to
223223+ adhere or comply, but it also shares criteria with them with which to evaluate the decision and
224224+ its importance.
225225+226226+- **Explicitly pass options to library functions at the call site, instead of relying on the
227227+ defaults**. For example, write `@prefetch(a, .{ .cache = .data, .rw = .read, .locality = 3 });`
228228+ over `@prefetch(a, .{});`. This improves readability but most of all avoids latent, potentially
229229+ catastrophic bugs in case the library ever changes its defaults.
230230+231231+## Performance
232232+233233+> โThe lack of back-of-the-envelope performance sketches is the root of all evil.โ โ Rivacindela
234234+> Hudsoni
235235+236236+- Think about performance from the outset, from the beginning. **The best time to solve performance,
237237+ to get the huge 1000x wins, is in the design phase, which is precisely when we can't measure or
238238+ profile.** It's also typically harder to fix a system after implementation and profiling, and the
239239+ gains are less. So you have to have mechanical sympathy. Like a carpenter, work with the grain.
240240+241241+- **Perform back-of-the-envelope sketches with respect to the four resources (network, disk, memory,
242242+ CPU) and their two main characteristics (bandwidth, latency).** Sketches are cheap. Use sketches
243243+ to be โroughly rightโ and land within 90% of the global maximum.
244244+245245+- Optimize for the slowest resources first (network, disk, memory, CPU) in that order, after
246246+ compensating for the frequency of usage, because faster resources may be used many times more. For
247247+ example, a memory cache miss may be as expensive as a disk fsync, if it happens many times more.
248248+249249+- Distinguish between the control plane and data plane. A clear delineation between control plane
250250+ and data plane through the use of batching enables a high level of assertion safety without losing
251251+ performance. See our [July 2021 talk on Zig SHOWTIME](https://youtu.be/BH2jvJ74npM?t=1958) for
252252+ examples.
253253+254254+- Amortize network, disk, memory and CPU costs by batching accesses.
255255+256256+- Let the CPU be a sprinter doing the 100m. Be predictable. Don't force the CPU to zig zag and
257257+ change lanes. Give the CPU large enough chunks of work. This comes back to batching.
258258+259259+- Be explicit. Minimize dependence on the compiler to do the right thing for you.
260260+261261+ In particular, extract hot loops into stand-alone functions with primitive arguments without
262262+ `self` (see [an example](https://github.com/tigerbeetle/tigerbeetle/blob/0.16.19/src/lsm/compaction.zig#L1932-L1937)).
263263+ That way, the compiler doesn't need to prove that it can cache struct's fields in registers, and a
264264+ human reader can spot redundant computations easier.
265265+266266+## Developer Experience
267267+268268+> โThere are only two hard things in Computer Science: cache invalidation, naming things, and
269269+> off-by-one errors.โ โ Phil Karlton
270270+271271+### Naming Things
272272+273273+- **Get the nouns and verbs just right.** Great names are the essence of great code, they capture
274274+ what a thing is or does, and provide a crisp, intuitive mental model. They show that you
275275+ understand the domain. Take time to find the perfect name, to find nouns and verbs that work
276276+ together, so that the whole is greater than the sum of its parts.
277277+278278+- Use `snake_case` for function, variable, and file names. The underscore is the closest thing we
279279+ have as programmers to a space, and helps to separate words and encourage descriptive names. We
280280+ don't use Zig's `CamelCase.zig` style for "struct" files to keep the convention simple and
281281+ consistent.
282282+283283+- Do not abbreviate variable names, unless the variable is a primitive integer type used as an
284284+ argument to a sort function or matrix calculation. Use long form arguments in scripts: `--force`,
285285+ not `-f`. Single letter flags are for interactive usage.
286286+287287+- Use proper capitalization for acronyms (`VSRState`, not `VsrState`).
288288+289289+- For the rest, follow the Zig style guide.
290290+291291+- Add units or qualifiers to variable names, and put the units or qualifiers last, sorted by
292292+ descending significance, so that the variable starts with the most significant word, and ends with
293293+ the least significant word. For example, `latency_ms_max` rather than `max_latency_ms`. This will
294294+ then line up nicely when `latency_ms_min` is added, as well as group all variables that relate to
295295+ latency.
296296+297297+- Infuse names with meaning. For example, `allocator: Allocator` is a good, if boring name,
298298+ but `gpa: Allocator` and `arena: Allocator` are excellent. They inform the reader whether
299299+ `deinit` should be called explicitly.
300300+301301+- When choosing related names, try hard to find names with the same number of characters so that
302302+ related variables all line up in the source. For example, as arguments to a memcpy function,
303303+ `source` and `target` are better than `src` and `dest` because they have the second-order effect
304304+ that any related variables such as `source_offset` and `target_offset` will all line up in
305305+ calculations and slices. This makes the code symmetrical, with clean blocks that are easier for
306306+ the eye to parse and for the reader to check.
307307+308308+- When a single function calls out to a helper function or callback, prefix the name of the helper
309309+ function with the name of the calling function to show the call history. For example,
310310+ `read_sector()` and `read_sector_callback()`.
311311+312312+- Callbacks go last in the list of parameters. This mirrors control flow: callbacks are also
313313+ _invoked_ last.
314314+315315+- _Order_ matters for readability (even if it doesn't affect semantics). On the first read, a file
316316+ is read top-down, so put important things near the top. The `main` function goes first.
317317+318318+ The same goes for `structs`, the order is fields then types then methods:
319319+320320+ ```zig
321321+ time: Time,
322322+ process_id: ProcessID,
323323+324324+ const ProcessID = struct { cluster: u128, replica: u8 };
325325+ const Tracer = @This(); // This alias concludes the types section.
326326+327327+ pub fn init(gpa: std.mem.Allocator, time: Time) !Tracer {
328328+ ...
329329+ }
330330+ ```
331331+332332+ If a nested type is complex, make it a top-level struct.
333333+334334+ At the same time, not everything has a single right order. When in doubt, consider sorting
335335+ alphabetically, taking advantage of big-endian naming.
336336+337337+- Don't overload names with multiple meanings that are context-dependent. For example, TigerBeetle
338338+ has a feature called _pending transfers_ where a pending transfer can be subsequently _posted_ or
339339+ _voided_. At first, we called them _two-phase commit transfers_, but this overloaded the
340340+ _two-phase commit_ terminology that was used in our consensus protocol, causing confusion.
341341+342342+- Think of how names will be used outside the code, in documentation or communication. For example,
343343+ a noun is often a better descriptor than an adjective or present participle, because a noun can be
344344+ directly used in correspondence without having to be rephrased. Compare `replica.pipeline` vs
345345+ `replica.preparing`. The former can be used directly as a section header in a document or
346346+ conversation, whereas the latter must be clarified. Noun names compose more clearly for derived
347347+ identifiers, e.g. `config.pipeline_max`.
348348+349349+- Zig has named arguments through the `options: struct` pattern. Use it when arguments can be
350350+ mixed up. A function taking two `u64` must use an options struct. If an argument can be `null`,
351351+ it should be named so that the meaning of `null` literal at the call site is clear.
352352+353353+ Because dependencies like an allocator or a tracer are singletons with unique types, they should
354354+ be threaded through constructors positionally, from the most general to the most specific.
355355+356356+- **Write descriptive commit messages** that inform and delight the reader, because your commit
357357+ messages are being read. Note that a pull request description is not stored in the git repository
358358+ and is invisible in `git blame`, and therefore is not a replacement for a commit message.
359359+360360+- Don't forget to say why. Code alone is not documentation. Use comments to explain why you wrote
361361+ the code the way you did. Show your workings.
362362+363363+- Don't forget to say how. For example, when writing a test, think of writing a description at the
364364+ top to explain the goal and methodology of the test, to help your reader get up to speed, or to
365365+ skip over sections, without forcing them to dive in.
366366+367367+- Comments are sentences, with a space after the slash, with a capital letter and a full stop, or a
368368+ colon if they relate to something that follows. Comments are well-written prose describing the
369369+ code, not just scribblings in the margin. Comments after the end of a line _can_ be phrases, with
370370+ no punctuation.
371371+372372+### Cache Invalidation
373373+374374+- Don't duplicate variables or take aliases to them. This will reduce the probability that state
375375+ gets out of sync.
376376+377377+- If you don't mean a function argument to be copied when passed by value, and if the argument type
378378+ is more than 16 bytes, then pass the argument as `*const`. This will catch bugs where the caller
379379+ makes an accidental copy on the stack before calling the function.
380380+381381+- Construct larger structs _in-place_ by passing an _out pointer_ during initialization.
382382+383383+ In-place initializations can assume **pointer stability** and **immovable types** while
384384+ eliminating intermediate copy-move allocations, which can lead to undesirable stack growth.
385385+386386+ Keep in mind that in-place initializations are viral โ if any field is initialized
387387+ in-place, the entire container struct should be initialized in-place as well.
388388+389389+ **Prefer:**
390390+ ```zig
391391+ fn init(target: *LargeStruct) !void {
392392+ target.* = .{
393393+ // in-place initialization.
394394+ };
395395+ }
396396+397397+ fn main() !void {
398398+ var target: LargeStruct = undefined;
399399+ try target.init();
400400+ }
401401+ ```
402402+403403+ **Over:**
404404+ ```zig
405405+ fn init() !LargeStruct {
406406+ return LargeStruct {
407407+ // moving the initialized object.
408408+ }
409409+ }
410410+411411+ fn main() !void {
412412+ var target = try LargeStruct.init();
413413+ }
414414+ ```
415415+416416+- **Shrink the scope** to minimize the number of variables at play and reduce the probability that
417417+ the wrong variable is used.
418418+419419+- Calculate or check variables close to where/when they are used. **Don't introduce variables before
420420+ they are needed.** Don't leave them around where they are not. This will reduce the probability of
421421+ a POCPOU (place-of-check to place-of-use), a distant cousin to the infamous
422422+ [TOCTOU](https://en.wikipedia.org/wiki/Time-of-check_to_time-of-use). Most bugs come down to a
423423+ semantic gap, caused by a gap in time or space, because it's harder to check code that's not
424424+ contained along those dimensions.
425425+426426+- Use simpler function signatures and return types to reduce dimensionality at the call site, the
427427+ number of branches that need to be handled at the call site, because this dimensionality can also
428428+ be viral, propagating through the call chain. For example, as a return type, `void` trumps `bool`,
429429+ `bool` trumps `u64`, `u64` trumps `?u64`, and `?u64` trumps `!u64`.
430430+431431+- Ensure that functions run to completion without suspending, so that precondition assertions are
432432+ true throughout the lifetime of the function. These assertions are useful documentation without a
433433+ suspend, but may be misleading otherwise.
434434+435435+- Be on your guard for **[buffer bleeds](https://en.wikipedia.org/wiki/Heartbleed)**. This is a
436436+ buffer underflow, the opposite of a buffer overflow, where a buffer is not fully utilized, with
437437+ padding not zeroed correctly. This may not only leak sensitive information, but may cause
438438+ deterministic guarantees as required by TigerBeetle to be violated.
439439+440440+- Use newlines to **group resource allocation and deallocation**, i.e. before the resource
441441+ allocation and after the corresponding `defer` statement, to make leaks easier to spot.
442442+443443+### Off-By-One Errors
444444+445445+- **The usual suspects for off-by-one errors are casual interactions between an `index`, a `count`
446446+ or a `size`.** These are all primitive integer types, but should be seen as distinct types, with
447447+ clear rules to cast between them. To go from an `index` to a `count` you need to add one, since
448448+ indexes are _0-based_ but counts are _1-based_. To go from a `count` to a `size` you need to
449449+ multiply by the unit. Again, this is why including units and qualifiers in variable names is
450450+ important.
451451+452452+- Show your intent with respect to division. For example, use `@divExact()`, `@divFloor()` or
453453+ `div_ceil()` to show the reader you've thought through all the interesting scenarios where
454454+ rounding may be involved.
455455+456456+### Style By The Numbers
457457+458458+- Run `zig fmt`.
459459+460460+- Use 4 spaces of indentation, rather than 2 spaces, as that is more obvious to the eye at a
461461+ distance.
462462+463463+- Hard limit all line lengths, without exception, to at most 100 columns for a good typographic
464464+ "measure". Use it up. Never go beyond. Nothing should be hidden by a horizontal scrollbar. Let
465465+ your editor help you by setting a column ruler. To wrap a function signature, call or data
466466+ structure, add a trailing comma, close your eyes and let `zig fmt` do the rest.
467467+468468+ Similar to function length, the motivation behind the number 100 is physical: just enough
469469+ to fit two copies of the code side-by-side on a screen.
470470+471471+- Add braces to the `if` statement unless it fits on a single line for consistency and defense in
472472+ depth against "goto fail;" bugs.
473473+474474+### Dependencies
475475+476476+TigerBeetle has **a โzero dependenciesโ policy**, apart from the Zig toolchain. Dependencies, in
477477+general, inevitably lead to supply chain attacks, safety and performance risk, and slow install
478478+times. For foundational infrastructure in particular, the cost of any dependency is further
479479+amplified throughout the rest of the stack.
480480+481481+### Tooling
482482+483483+Similarly, tools have costs. A small standardized toolbox is simpler to operate than an array of
484484+specialized instruments each with a dedicated manual. Our primary tool is Zig. It may not be the
485485+best for everything, but it's good enough for most things. We invest into our Zig tooling to ensure
486486+that we can tackle new problems quickly, with a minimum of accidental complexity in our local
487487+development environment.
488488+489489+> โThe right tool for the job is often the tool you are already usingโadding new tools has a higher
490490+> cost than many people appreciateโ โ John Carmack
491491+492492+For example, the next time you write a script, instead of `scripts/*.sh`, write `scripts/*.zig`.
493493+494494+This not only makes your script cross-platform and portable, but introduces type safety and
495495+increases the probability that running your script will succeed for everyone on the team, instead of
496496+hitting a Bash/Shell/OS-specific issue.
497497+498498+Standardizing on Zig for tooling is important to ensure that we reduce dimensionality, as the team,
499499+and therefore the range of personal tastes, grows. This may be slower for you in the short term, but
500500+makes for more velocity for the team in the long term.
501501+502502+## The Last Stage
503503+504504+At the end of the day, keep trying things out, have fun, and rememberโit's called TigerBeetle, not
505505+only because it's fast, but because it's small!
506506+507507+> You donโt really suppose, do you, that all your adventures and escapes were managed by mere luck,
508508+> just for your sole benefit? You are a very fine person, Mr. Baggins, and I am very fond of you;
509509+> but you are only quite a little fellow in a wide world after all!โ
510510+>
511511+> โThank goodness!โ said Bilbo laughing, and handed him the tobacco-jar.