# Appendix I. Zig Coding Style Guide

## Appendix I. Zig Coding Style Guide

Zig code should be explicit, simple, and easy to inspect. The goal is not cleverness. The goal is code that another programmer can read, verify, and maintain.

### I.1 Prefer `const` by Default

Use `const` unless the value must change.

```zig
const x = 10;
```

Use `var` only when mutation is required.

```zig
var count: usize = 0;
count += 1;
```

This makes the code easier to reason about. When a value is `const`, the reader knows it will not be reassigned.

### I.2 Give Types When They Help

Zig can infer many types.

```zig
const x = 10;
```

But explicit types help when the exact size matters.

```zig
const port: u16 = 8080;
const count: usize = items.len;
```

Use explicit types for public APIs, integer widths, file formats, protocols, and data structures.

### I.3 Keep Functions Small

A good Zig function should do one clear job.

Bad:

```zig
fn run() !void {
    // parse args
    // open files
    // allocate buffers
    // process data
    // write output
}
```

Better:

```zig
fn run() !void {
    const config = try parseArgs();
    const input = try readInput(config);
    const result = try process(input);
    try writeOutput(result);
}
```

Small functions make error paths, ownership, and cleanup easier to see.

### I.4 Put Cleanup Near Acquisition

Use `defer` immediately after acquiring a resource.

```zig
const file = try std.fs.cwd().openFile("data.txt", .{});
defer file.close();
```

For allocated memory:

```zig
const buffer = try allocator.alloc(u8, 1024);
defer allocator.free(buffer);
```

This reduces leaks and makes ownership visible.

### I.5 Use `errdefer` for Partial Construction

When a function builds an object in stages, use `errdefer`.

```zig
fn createUser(allocator: std.mem.Allocator, name: []const u8) !User {
    const owned_name = try allocator.dupe(u8, name);
    errdefer allocator.free(owned_name);

    return User{
        .name = owned_name,
    };
}
```

If the function fails before returning, `errdefer` cleans up.

### I.6 Pass Allocators Explicitly

A function that may allocate should usually take an allocator.

```zig
fn readName(allocator: std.mem.Allocator) ![]u8 {
    return try allocator.dupe(u8, "zig");
}
```

This tells the caller that memory ownership is involved.

Avoid hidden global allocation.

### I.7 Document Ownership

When returning allocated memory, say who frees it.

```zig
/// Returns newly allocated memory.
/// Caller owns the returned slice and must free it with `allocator.free`.
fn copyText(allocator: std.mem.Allocator, text: []const u8) ![]u8 {
    return try allocator.dupe(u8, text);
}
```

When returning borrowed memory, say how long it lives.

```zig
/// Returns a slice pointing into `input`.
/// The returned slice must not outlive `input`.
fn firstWord(input: []const u8) []const u8 {
    // ...
}
```

### I.8 Prefer Slices for Buffers

Use slices when passing many items.

```zig
fn sum(values: []const i32) i32 {
    var total: i32 = 0;

    for (values) |value| {
        total += value;
    }

    return total;
}
```

A slice carries both pointer and length. That is usually safer than a raw pointer plus a separate length.

### I.9 Avoid Long-Lived Borrowed Slices

Do not store a slice unless you know the memory behind it will stay alive.

Risky:

```zig
const User = struct {
    name: []const u8,
};
```

This is safe only if `name` points to memory that outlives the `User`.

For long-lived data, copy the bytes:

```zig
user.name = try allocator.dupe(u8, input_name);
```

### I.10 Keep Error Handling Visible

Do not hide errors unnecessarily.

Use `try` when the current function should return the error.

```zig
try saveFile(path, data);
```

Use `catch` when this function can handle it.

```zig
saveFile(path, data) catch |err| {
    std.log.err("failed to save file: {}", .{err});
    return err;
};
```

Avoid empty catches unless ignoring the error is truly safe.

```zig
doSomething() catch {};
```

That code should be rare.

### I.11 Use Specific Errors

Prefer meaningful error names.

```zig
const ConfigError = error{
    MissingName,
    InvalidPort,
    UnknownOption,
};
```

This is better than returning a vague failure everywhere.

### I.12 Avoid Clever `comptime`

`comptime` is powerful, but it can make code harder to read.

Good use:

```zig
fn max(comptime T: type, a: T, b: T) T {
    return if (a > b) a else b;
}
```

Risky use:

```zig
// large compile-time code generator
// hard to debug
// hard to understand
```

Use compile-time programming when it removes repetition or enforces correctness. Avoid it when ordinary runtime code is clearer.

### I.13 Use Clear Names

Prefer names that explain meaning.

Good:

```zig
const request_count = 10;
const input_path = "data.txt";
const allocator = arena.allocator();
```

Weak:

```zig
const x = 10;
const s = "data.txt";
const a = arena.allocator();
```

Short names are fine in small scopes:

```zig
for (items) |item| {
    // item is clear here
}
```

### I.14 Keep Public APIs Boring

Public APIs should be predictable.

Good:

```zig
pub fn parseConfig(allocator: std.mem.Allocator, text: []const u8) !Config
```

This signature shows:

The function may allocate.

The function reads text.

The function may fail.

The function returns `Config`.

Avoid APIs where allocation, failure, or ownership is hidden.

### I.15 Prefer Plain Data

Zig works well with plain structs.

```zig
const Point = struct {
    x: f64,
    y: f64,
};
```

Add methods when they improve readability.

```zig
const Point = struct {
    x: f64,
    y: f64,

    fn lengthSquared(self: Point) f64 {
        return self.x * self.x + self.y * self.y;
    }
};
```

Do not force object-oriented patterns where plain data is enough.

### I.16 Use Tagged Unions for Variants

When a value can be one of several shapes, use `union(enum)`.

```zig
const Token = union(enum) {
    number: i64,
    identifier: []const u8,
    plus,
    minus,
};
```

Then handle it with `switch`.

```zig
switch (token) {
    .number => |n| std.debug.print("{}\n", .{n}),
    .identifier => |name| std.debug.print("{s}\n", .{name}),
    .plus => {},
    .minus => {},
}
```

This is safer than manually tracking a tag and payload separately.

### I.17 Avoid Global Mutable State

Global mutable state makes programs harder to test and reason about.

Prefer passing state explicitly.

```zig
fn run(config: Config, allocator: std.mem.Allocator) !void {
    // ...
}
```

Instead of hiding dependencies in globals.

### I.18 Keep Imports Simple

Put imports near the top.

```zig
const std = @import("std");
const parser = @import("parser.zig");
```

Avoid deeply nested import tricks. A file should make its dependencies easy to see.

### I.19 Use Tests Near the Code

Zig lets you place tests in the same file.

```zig
fn add(a: i32, b: i32) i32 {
    return a + b;
}

test "add returns the sum" {
    try std.testing.expectEqual(@as(i32, 5), add(2, 3));
}
```

Small tests close to the code help future changes.

### I.20 Format Code Consistently

Use:

```bash
zig fmt .
```

Zig has a standard formatter. Use it.

Do not hand-format code into a personal style that fights the formatter.

### I.21 Check Return Values

If a function returns a value, either use it or explicitly discard it.

```zig
_ = c.printf("hello\n");
```

The discard shows intent.

Do not leave unused values by accident.

### I.22 Write Comments for Why, Not Noise

Bad comment:

```zig
// increment i
i += 1;
```

Good comment:

```zig
// Keep one byte for the trailing zero required by the C API.
const usable_len = buffer.len - 1;
```

Comments should explain decisions, invariants, ownership, or non-obvious constraints.

### I.23 Prefer Simple Control Flow

Deep nesting makes error paths hard to follow.

Harder:

```zig
if (valid) {
    if (ready) {
        if (allowed) {
            try run();
        }
    }
}
```

Clearer:

```zig
if (!valid) return error.Invalid;
if (!ready) return error.NotReady;
if (!allowed) return error.NotAllowed;

try run();
```

Early returns are often clearer in Zig.

### I.24 Keep Unsafe Code Small

Pointer casts, manual alignment, C interop, volatile memory, and packed layouts need care.

Put risky code behind a small API.

```zig
fn readHeader(bytes: []const u8) !Header {
    // unsafe or low-level details stay here
}
```

Then the rest of the program uses the safe wrapper.

### I.25 Style Rule Summary

Good Zig style means:

| Practice | Reason |
|---|---|
| Prefer `const` | Reduces accidental mutation |
| Pass allocators | Makes allocation visible |
| Use `defer` | Keeps cleanup reliable |
| Use slices | Keeps pointer plus length together |
| Handle errors explicitly | Makes failure visible |
| Keep functions small | Makes ownership and control flow clearer |
| Use `zig fmt` | Keeps style consistent |
| Avoid hidden globals | Improves testing and reasoning |
| Document ownership | Prevents memory bugs |

Zig style follows Zig’s main idea: make the important parts of the program visible.

