Skip to content

`@panic`

@panic stops the program immediately with a message.

@panic

@panic stops the program immediately with a message.

It is used when the program reaches a state that should never happen, or when continuing would be unsafe.

@panic("something went wrong");

This tells Zig:

Stop the program here.
Print this message.

A Simple Example

const std = @import("std");

pub fn main() void {
    @panic("this program cannot continue");

    std.debug.print("after panic\n", .{});
}

The second print does not run. The program stops at @panic.

Panic Means Program Failure

A panic is not normal error handling.

Use errors for expected failure:

fn openConfig() !void {
    return error.FileNotFound;
}

Use panic for programmer mistakes or impossible states:

fn getModeName(mode: u8) []const u8 {
    return switch (mode) {
        0 => "read",
        1 => "write",
        2 => "execute",
        else => @panic("invalid mode"),
    };
}

Here, the code assumes only 0, 1, and 2 are valid modes. If another value appears, the program is already in a bad state.

Prefer Errors for Recoverable Problems

This is usually bad API design:

fn parseNumber(text: []const u8) u32 {
    if (text.len == 0) {
        @panic("empty input");
    }

    return 0;
}

Empty input is not impossible. It is a normal user input case.

A better version returns an error:

fn parseNumber(text: []const u8) !u32 {
    if (text.len == 0) {
        return error.EmptyInput;
    }

    return 0;
}

Now the caller can handle the failure.

Panic Is Useful for Broken Assumptions

A panic is reasonable when an internal invariant is broken.

const Stack = struct {
    items: [16]u8,
    len: usize,

    fn pop(self: *Stack) u8 {
        if (self.len == 0) {
            @panic("pop from empty stack");
        }

        self.len -= 1;
        return self.items[self.len];
    }
};

This version treats popping from an empty stack as a programmer error.

A safer public API would return an optional or an error:

fn pop(self: *Stack) ?u8 {
    if (self.len == 0) return null;

    self.len -= 1;
    return self.items[self.len];
}

@panic Does Not Return

After @panic, normal control flow ends.

This is why it can be used in places where a value is expected:

fn name(id: u8) []const u8 {
    return switch (id) {
        1 => "one",
        2 => "two",
        else => @panic("unknown id"),
    };
}

The function returns []const u8.

The else branch does not return a string. It stops the program. Because it never returns, Zig can accept it in a value-producing expression.

Panic Message

The panic message should explain the failed assumption.

Poor message:

@panic("bad");

Better message:

@panic("expected non-empty stack");

The goal is to help the programmer find the bug quickly.

@panic vs unreachable

Use @panic when you want an explicit runtime failure with a message.

Use unreachable when you are telling Zig that a code path cannot happen.

else => unreachable,

unreachable is a stronger claim. It says the path is impossible. In safe modes, reaching it causes a safety panic. In optimized unsafe modes, the compiler may assume it never happens and optimize based on that assumption.

For beginner code, prefer @panic when you want a clear message.

Key Idea

@panic(message) stops the program.

Use it for impossible states, broken internal assumptions, and unrecoverable programmer errors.

Do not use it for normal failure that the caller should handle. Use Zig errors or optionals for those cases.