# Inline Branching

### Inline Branching

Inline branching means Zig chooses a branch during compilation, not during runtime.

This is closely related to `comptime`. When a condition is known at compile time, Zig can decide which path to keep before the final program is built.

The result is specialized code.

#### Runtime Branching

A normal `if` usually runs at runtime.

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

pub fn main() void {
    var x: i32 = 10;

    if (x > 5) {
        std.debug.print("large\n", .{});
    } else {
        std.debug.print("small\n", .{});
    }
}
```

Here, `x` is a runtime variable.

The program checks `x > 5` while it is running. The compiled program must contain code for the condition and both possible paths.

Even if `x` starts as `10`, it is still a `var`, so Zig treats it as runtime data.

#### Compile-Time Branching

Now compare this:

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

fn printByType(comptime T: type) void {
    if (T == i32) {
        std.debug.print("i32\n", .{});
    } else {
        std.debug.print("other\n", .{});
    }
}

pub fn main() void {
    printByType(i32);
}
```

The value `T` is known during compilation.

So the compiler already knows this condition:

```zig
T == i32
```

For the call `printByType(i32)`, the compiler keeps the first branch.

Conceptually, the generated function behaves like this:

```zig
fn printByTypeForI32() void {
    std.debug.print("i32\n", .{});
}
```

The unused branch is not needed for this specialization.

#### Why This Matters

Inline branching lets you write one generic function and still get specific code for each type.

For example, suppose you want a function that describes a value differently depending on its type:

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

fn describe(comptime T: type, value: T) void {
    if (T == bool) {
        std.debug.print("boolean: {}\n", .{value});
    } else if (T == i32) {
        std.debug.print("integer: {}\n", .{value});
    } else {
        std.debug.print("other value\n", .{});
    }
}

pub fn main() void {
    describe(bool, true);
    describe(i32, 123);
}
```

The compiler creates specialized behavior for each call.

For `describe(bool, true)`, it uses the `bool` branch.

For `describe(i32, 123)`, it uses the `i32` branch.

There is no need for a runtime type check. The type is already known before the program runs.

#### Branches Can Return Different Code Shapes

Compile-time branching can select different code, not just different values.

```zig
fn maxValue(comptime T: type) T {
    if (T == u8) {
        return 255;
    } else if (T == u16) {
        return 65535;
    } else {
        @compileError("unsupported integer type");
    }
}
```

Usage:

```zig
const a = maxValue(u8);
const b = maxValue(u16);
```

For `u8`, the function returns `255`.

For `u16`, the function returns `65535`.

For another type, compilation fails:

```zig
const c = maxValue(bool);
```

The compiler rejects this because `bool` reaches the `@compileError` branch.

#### `@compileError` in Branches

`@compileError` is useful with inline branching because it lets you enforce rules at compile time.

```zig
fn onlyUnsigned(comptime T: type) void {
    if (T != u8 and T != u16 and T != u32 and T != u64) {
        @compileError("expected an unsigned integer type");
    }
}
```

This call is accepted:

```zig
onlyUnsigned(u32);
```

This call is rejected:

```zig
onlyUnsigned(i32);
```

The error happens during compilation, before the program runs.

This is better than letting invalid code exist and fail later.

#### Branching on Values

Inline branching is not limited to types.

You can branch on any compile-time known value.

```zig
fn repeatMessage(comptime loud: bool) void {
    if (loud) {
        @compileLog("LOUD MODE");
    } else {
        @compileLog("quiet mode");
    }
}
```

Usage:

```zig
comptime {
    repeatMessage(true);
    repeatMessage(false);
}
```

The compiler knows the value of `loud` in each call.

So it chooses the branch immediately.

#### Runtime Values Cannot Control Compile-Time Branches

This does not work:

```zig
fn choose(comptime flag: bool) void {
    if (flag) {
        // compile-time selected path
    }
}

pub fn main() void {
    var runtime_flag = true;
    choose(runtime_flag);
}
```

The parameter `flag` is marked `comptime`.

That means the caller must pass a compile-time known value.

But `runtime_flag` is a runtime variable. Zig cannot use it to choose compile-time code.

The fix is to pass a constant known at compile time:

```zig
choose(true);
```

Or make the function use runtime branching:

```zig
fn choose(flag: bool) void {
    if (flag) {
        // runtime selected path
    }
}
```

#### Inline Branching with `switch`

`switch` also works well with compile-time values.

```zig
fn typeName(comptime T: type) []const u8 {
    return switch (T) {
        bool => "bool",
        u8 => "u8",
        i32 => "i32",
        f64 => "f64",
        else => "unknown",
    };
}
```

Usage:

```zig
const name = typeName(i32);
```

The compiler knows `T`, so it can choose the `i32` branch during compilation.

The result is:

```zig
"i32"
```

#### Inline Branching and Dead Code

Compile-time branching can remove code that does not apply.

Example:

```zig
fn debugPrint(comptime enabled: bool, message: []const u8) void {
    if (enabled) {
        @import("std").debug.print("{s}\n", .{message});
    }
}
```

If you call:

```zig
debugPrint(false, "hello");
```

Then the compiler knows `enabled` is `false`.

The function body does nothing for that specialization.

This pattern is useful for optional features, debug behavior, and configuration flags.

#### A Common Beginner Mistake

Beginners often expect `const` to always mean compile time.

It does not.

This value is compile-time known:

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

But this value may be runtime data:

```zig
const input = readNumberFromUser();
```

The variable `input` cannot be changed after assignment, but its value comes from runtime behavior.

So this does not make sense:

```zig
fn makeArray(comptime n: usize) [n]u8 {
    return undefined;
}

pub fn main() void {
    const input = readNumberFromUser();
    const buffer = makeArray(input);
    _ = buffer;
}
```

The array size must be known during compilation.

User input arrives only after the program starts.

#### Inline Branching Does Not Mean Faster by Default

Inline branching can remove runtime checks, but that does not mean every branch should be compile-time.

Use compile-time branching when the decision is naturally known before runtime:

types

array sizes

feature flags

build options

static configuration

format descriptions

protocol layouts

Do not force ordinary runtime data into `comptime`.

Runtime data belongs at runtime.

#### Practical Example: Type-Based Formatting

Here is a small example that prints values differently by type:

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

fn printValue(comptime T: type, value: T) void {
    if (T == bool) {
        std.debug.print("bool = {}\n", .{value});
    } else if (T == u8) {
        std.debug.print("byte = {}\n", .{value});
    } else if (T == []const u8) {
        std.debug.print("text = {s}\n", .{value});
    } else {
        @compileError("unsupported type");
    }
}

pub fn main() void {
    printValue(bool, true);
    printValue(u8, 65);
    printValue([]const u8, "hello");
}
```

This function has one source definition, but the compiler specializes it for each type.

If you call it with an unsupported type:

```zig
printValue(f64, 3.14);
```

Compilation fails with your custom error.

#### Mental Model

Inline branching lets Zig answer a question early.

Runtime branch:

```text
When the program runs, check this condition.
```

Compile-time branch:

```text
While compiling, choose the correct code path.
```

That is the core idea.

Use inline branching when the condition belongs to the structure of the program, not to live input from the user, network, file system, clock, or operating system.

