Error Set Type
In Zig, error values are explicitly defined as error set types. Writing error{Foo, Bar} declares a set of error values as a type, and by combining it with !T (an error union) as a function's return type, you can handle "success returns T, failure returns an error value" in a type-safe way. Error set types have a structure similar to enums, but they also support merging multiple sets with || and accepting them as anyerror (a superset of all errors), making them flexible to use.
Syntax
// -----------------------------------------------
// Defining an error set type
// -----------------------------------------------
// Use error{value1, value2, ...} to define an error set type
const MyError = error{
NotFound, // Error values start with an uppercase letter
InvalidInput,
Overflow,
};
// -----------------------------------------------
// Functions with an error union type !T
// -----------------------------------------------
// The return type !T means "either T or one of MyError"
fn functionName(arg: Type) MyError!T {
if (condition) return error.InvalidInput; // Return an error with error.valueName
return normalValue;
}
// Inferred error set (!T) — the compiler infers the error set
fn functionName2(arg: Type) !T {
// ...
}
// -----------------------------------------------
// Handling an error union
// -----------------------------------------------
// try — propagates the error to the caller if one occurs (most common pattern)
const value = try functionName(arg);
// catch — catches the error and returns a fallback value
const value2 = functionName(arg) catch defaultValue;
// catch |err| — receives the error variable and handles it
const value3 = functionName(arg) catch |err| blk: {
std.debug.print("Error: {}\n", .{err});
break :blk defaultValue;
};
// Use switch to branch on the error
const result = functionName(arg) catch |err| switch (err) {
error.NotFound => defaultValueA,
error.InvalidInput => defaultValueB,
else => return err, // Re-propagate unexpected errors
};
// -----------------------------------------------
// Merging error sets (|| operator)
// -----------------------------------------------
const CombinedError = ErrorSetA || ErrorSetB; // Merges both sets
// -----------------------------------------------
// anyerror — a superset of all error values
// -----------------------------------------------
fn genericFunction(arg: Type) anyerror!T { ... }
Error set operations summary
| Syntax / Type | Description |
|---|---|
error{A, B} | Defines an error set type inline. Creates a type that groups multiple error values. |
const E = error{A, B}; | Gives the error set type a name so it can be reused. |
error.A | Produces an error value. Can be returned from a function as an error set type value. |
E!T | An error union type. Represents "either T or one of the errors in set E". |
!T | An inferred error union type. The compiler automatically infers the error set. |
try expr | Propagates the error to the caller if the expression fails, or returns the unwrapped value on success. |
expr catch val | Uses val as a fallback when an error occurs. |
expr catch |err| { ... } | Receives the error variable err and handles it in a block. |
A || B | Merges two error set types into a new error set type. |
anyerror | A superset type that includes all error values. Used in generic functions and interfaces. |
@errorName(err) | Converts an error value to a string slice ([]const u8). |
@typeInfo(E).ErrorSet | Retrieves metadata about an error set type (such as the list of values) at compile time. |
Sample code
evangelion_error_set.zig
// evangelion_error_set.zig — sample code for error set types
// Uses Evangelion characters to demonstrate
// error{} definitions, error unions, try/catch, and || merging
const std = @import("std");
// -----------------------------------------------
// Defining error set types
// -----------------------------------------------
// Errors that can occur at the NERV command center
const NervError = error{
PilotNotFound, // No pilot was found
SyncRatioTooLow, // Sync ratio is below the required threshold
ATFieldFailure, // AT Field deployment failed
};
// Errors that can occur during the Eva activation sequence (defined as a separate set)
const ActivationError = error{
CoreLockout, // Access to the core is locked
PowerSupplyLost, // Power supply was cut off
};
// Merge the two error sets with || (represents errors for the entire launch sequence)
const LaunchError = NervError || ActivationError;
// -----------------------------------------------
// Functions that return an error union
// -----------------------------------------------
// Validates a pilot's sync ratio (returns an error if too low)
fn validateSyncRatio(pilot_name: []const u8, ratio: f32) NervError!f32 {
if (ratio < 40.0) {
std.debug.print(
" [{s}] Sync ratio {d:.1}% — below threshold, returning error\n",
.{ pilot_name, ratio },
);
return error.SyncRatioTooLow;
}
std.debug.print(
" [{s}] Sync ratio {d:.1}% — OK\n",
.{ pilot_name, ratio },
);
return ratio;
}
// Seats a pilot in the entry plug (returns NotFound if the name is empty)
fn seatPilot(pilot_name: []const u8) NervError![]const u8 {
if (pilot_name.len == 0) return error.PilotNotFound;
std.debug.print(" [{s}] Entry plug loaded\n", .{pilot_name});
return pilot_name;
}
// Runs a pre-launch check combining both functions above
// NervError!void — returns void on success (no value)
fn prelaunchCheck(pilot_name: []const u8, ratio: f32) NervError!void {
_ = try seatPilot(pilot_name); // Propagates the error to the caller if one occurs
_ = try validateSyncRatio(pilot_name, ratio);
std.debug.print(" [{s}] Pre-launch check complete\n", .{pilot_name});
}
// -----------------------------------------------
// Error handling patterns using catch
// -----------------------------------------------
// Returns the sync ratio, or 0.0 as a default on error
fn getSyncRatioOrDefault(pilot_name: []const u8, ratio: f32) f32 {
return validateSyncRatio(pilot_name, ratio) catch 0.0;
}
// Returns the sync ratio, logging the error before returning the default
fn getSyncRatioWithLog(pilot_name: []const u8, ratio: f32) f32 {
return validateSyncRatio(pilot_name, ratio) catch |err| blk: {
// Convert the error value to a string with @errorName
std.debug.print(
" Caught error: {s}\n",
.{@errorName(err)},
);
break :blk -1.0;
};
}
// -----------------------------------------------
// Branching on errors with switch
// -----------------------------------------------
// Returns a response message for each NervError using switch
fn handleNervError(err: NervError) []const u8 {
return switch (err) {
error.PilotNotFound => "Pilot unknown — requesting emergency dispatch",
error.SyncRatioTooLow => "Sync ratio too low — switching to forced connection mode",
error.ATFieldFailure => "AT Field failure — deploying N2 mine as countermeasure",
};
}
pub fn main() !void {
const stdout = std.io.getStdOut().writer();
try stdout.print("=== NERV Third Tokyo City Operations Command Center ===\n\n", .{});
// -----------------------------------------------
// Propagation with try and the success case
// -----------------------------------------------
try stdout.print("--- Pre-launch check (success case) ---\n", .{});
// Shinji Ikari (Unit-01 pilot) — sync ratio is high enough, completes normally
prelaunchCheck("Shinji Ikari", 88.5) catch |err| {
try stdout.print(" Launch failed: {s}\n", .{handleNervError(err)});
};
try stdout.print("\n--- Pre-launch check (sync ratio too low) ---\n", .{});
// Kaworu Nagisa — sync ratio is too low, results in an error
prelaunchCheck("Kaworu Nagisa", 12.0) catch |err| {
try stdout.print(" Launch failed: {s}\n", .{handleNervError(err)});
};
try stdout.print("\n--- Pre-launch check (pilot unknown) ---\n", .{});
// Empty name — results in PilotNotFound error
prelaunchCheck("", 75.0) catch |err| {
try stdout.print(" Launch failed: {s}\n", .{handleNervError(err)});
};
// -----------------------------------------------
// Getting a default value with catch
// -----------------------------------------------
try stdout.print("\n--- Getting a default value with catch ---\n", .{});
// Rei Ayanami — sync ratio is too low, so the default value 0.0 is returned
const rei_ratio = getSyncRatioOrDefault("Rei Ayanami", 30.0);
try stdout.print(" Rei Ayanami sync ratio (adjusted): {d:.1}%\n", .{rei_ratio});
// Asuka Langley Soryu — high enough, so the actual value is returned
const asuka_ratio = getSyncRatioOrDefault("Asuka Langley Soryu", 95.3);
try stdout.print(" Asuka sync ratio: {d:.1}%\n", .{asuka_ratio});
// -----------------------------------------------
// Logging an error with catch |err|
// -----------------------------------------------
try stdout.print("\n--- Logging an error with catch |err| ---\n", .{});
const rei_ratio2 = getSyncRatioWithLog("Rei Ayanami", 22.0);
try stdout.print(" Result: {d:.1}\n", .{rei_ratio2});
// -----------------------------------------------
// Merging error sets (LaunchError = NervError || ActivationError)
// -----------------------------------------------
try stdout.print("\n--- Merging error sets (LaunchError) ---\n", .{});
// LaunchError holds values from both NervError and ActivationError
const launch_errors = [_]LaunchError{
error.PilotNotFound,
error.SyncRatioTooLow,
error.ATFieldFailure,
error.CoreLockout,
error.PowerSupplyLost,
};
for (launch_errors) |err| {
// Stringify the error value with @errorName
try stdout.print(" {s}\n", .{@errorName(err)});
}
}
zig run evangelion_error_set.zig === NERV Third Tokyo City Operations Command Center === --- Pre-launch check (success case) --- [Shinji Ikari] Entry plug loaded [Shinji Ikari] Sync ratio 88.5% — OK [Shinji Ikari] Pre-launch check complete --- Pre-launch check (sync ratio too low) --- [Kaworu Nagisa] Entry plug loaded [Kaworu Nagisa] Sync ratio 12.0% — below threshold, returning error Launch failed: Sync ratio too low — switching to forced connection mode --- Pre-launch check (pilot unknown) --- Launch failed: Pilot unknown — requesting emergency dispatch --- Getting a default value with catch --- [Rei Ayanami] Sync ratio 30.0% — below threshold, returning error Rei Ayanami sync ratio (adjusted): 0.0% [Asuka Langley Soryu] Sync ratio 95.3% — OK Asuka sync ratio: 95.3% --- Logging an error with catch |err| --- [Rei Ayanami] Sync ratio 22.0% — below threshold, returning error Caught error: SyncRatioTooLow Result: -1.0 --- Merging error sets (LaunchError) --- PilotNotFound SyncRatioTooLow ATFieldFailure CoreLockout PowerSupplyLost
Overview
Zig's error set type makes it possible to detect missing error handling and unexpected error values at compile time by explicitly expressing "which errors can occur" as a type. You define the set with error{...} and combine it with !T (an error union) to make a function's failure modes readable directly from its signature. try is the most concise way to propagate an error upward, while catch is used when you want to handle it inline. Merging error sets with || is useful for combining errors from multiple subsystems into a single type. anyerror is a highly versatile superset of all error values, but because the compiler cannot perform exhaustiveness checks on it, using a concrete error set type is recommended when possible. For converting error values to strings with @errorName and for guidance on when to use optional types instead, see optional (optional type).
If you find any errors or copyright issues, please contact us.