@compileError
@compileError stops compilation with a custom error message.
It does not stop the program while it is running. It stops the program from being built.
@compileError("message");This means:
The code is invalid. Show this message during compilation.A Simple Example
pub fn main() void {
comptime {
@compileError("this code is not ready yet");
}
}When you compile this program, Zig reports the message and refuses to build the executable.
Why This Is Useful
@compileError is useful when code can detect a problem before runtime.
For example, suppose a function only supports integer types:
fn requireInteger(comptime T: type) void {
switch (@typeInfo(T)) {
.int => {},
else => @compileError("expected an integer type"),
}
}Now this works:
requireInteger(u32);But this fails during compilation:
requireInteger([]const u8);That is better than letting invalid code continue and fail later.
Compile-Time Checks
A common pattern is:
comptime {
if (condition) {
@compileError("explanation");
}
}Example:
comptime {
if (@sizeOf(usize) != 8) {
@compileError("this program requires a 64-bit target");
}
}This checks the target while compiling.
If the target does not match the program’s assumptions, the build fails immediately.
Better Error Messages for Generic Code
Generic code can produce confusing compiler errors if you do not check inputs clearly.
Example:
fn first(comptime T: type, values: []const T) T {
return values[0];
}This works for normal slices, but maybe your API only wants integer elements.
You can enforce that:
fn firstInteger(comptime T: type, values: []const T) T {
switch (@typeInfo(T)) {
.int => {},
else => @compileError("firstInteger expects an integer element type"),
}
return values[0];
}Now the error message explains the rule directly.
@compileError Runs at Compile Time
This is valid:
comptime {
@compileError("stop here");
}This is also common inside compile-time branches:
fn maxValue(comptime T: type) T {
return switch (@typeInfo(T)) {
.int => std.math.maxInt(T),
else => @compileError("maxValue only supports integer types"),
};
}The else branch is used only when T is not an integer type.
It Is Not Runtime Error Handling
Do not use @compileError for user input, file errors, network errors, or data that is only known while the program runs.
This cannot work as intended:
fn parse(text: []const u8) void {
if (text.len == 0) {
@compileError("empty input");
}
}text.len is usually a runtime value. @compileError is for compile-time decisions.
Use an error instead:
fn parse(text: []const u8) !void {
if (text.len == 0) {
return error.EmptyInput;
}
}@compileError vs @panic
@compileError stops compilation.
@compileError("bad type");@panic stops the running program.
@panic("bad state");Use @compileError when the code itself is invalid.
Use @panic when the compiled program reaches an impossible or unrecoverable state.
A Practical Example
const std = @import("std");
fn printNumber(comptime T: type, value: T) void {
switch (@typeInfo(T)) {
.int, .float, .comptime_int, .comptime_float => {},
else => @compileError("printNumber expects a numeric type"),
}
std.debug.print("{}\n", .{value});
}
pub fn main() void {
printNumber(u32, 123);
printNumber(f64, 3.14);
}This function accepts only numeric types.
If someone tries:
printNumber([]const u8, "hello");the build fails with the custom message.
Key Idea
@compileError(message) stops the build with a custom message.
Use it when compile-time code detects invalid types, unsupported targets, impossible configurations, or unfinished compile-time branches.