Error Union Type (!T)
In Zig, an error union type is used to express at the type level that a function may return either a normal value or an error. Writing !T produces a type that means "a value of type T, or an error value," and the compiler prevents errors from being silently ignored. This page covers how to declare error union types, how to handle them with try / catch, and how to use anyerror.
Syntax
// -----------------------------------------------
// Defining an error set
// -----------------------------------------------
const ErrorSetName = error {
ErrorName1, // Enumerate error values
ErrorName2,
};
// -----------------------------------------------
// Declaring an error union type
// -----------------------------------------------
ErrorSetName!T // A type that returns either an error from ErrorSetName, or a value of type T
anyerror!T // A type that returns either any error, or a value of type T
// -----------------------------------------------
// A function that returns an error union
// -----------------------------------------------
fn funcName(arg: Type) ErrorSetName!ReturnType {
if (errorCondition) return error.ErrorName; // Return an error
return normalValue; // Return a normal value
}
// -----------------------------------------------
// try — propagates the error to the caller
// -----------------------------------------------
const value = try funcName(arg);
// If an error is returned, it is propagated out of the current function
// -----------------------------------------------
// catch — handles the error inline
// -----------------------------------------------
const value = funcName(arg) catch |err| {
// Handle the error using the error value err
return defaultValue;
};
const value = funcName(arg) catch defaultValue;
// Use a default value on error (when the error value itself is not needed)
// -----------------------------------------------
// Branching on an error union with if
// -----------------------------------------------
if (funcName(arg)) |normalValue| {
// Handle the normal value
} else |err| {
// Handle the error err
}
Syntax Reference
| Syntax / Method | Description |
|---|---|
error { A, B } | Defines an error set (an enumeration of error values). |
ErrSet!T | An error union type that represents either an error from the specified error set, or a value of type T. |
anyerror!T | An error union type that represents either any error, or a value of type T. Used when you do not want to restrict the error set. |
!T | When used in a function signature, the compiler infers the error set automatically. |
return error.Name | Returns an error value. Specify an error name that belongs to the error set. |
try expr | Propagates an error to the caller if one is returned. Returns the normal value if there is no error. |
expr catch |err| { ... } | Catches and handles an error inline. The error value is bound to err. |
expr catch defaultValue | Returns a default value on error. A shorthand form when you do not need to inspect the error value. |
if (expr) |v| { } else |err| { } | Branches on an error union, handling the normal value and the error separately. |
errdefer stmt | Registers a statement to be executed when leaving the scope only if an error occurred. |
Sample Code
error_union.zig
// error_union.zig — Sample code for Zig error union types
// Uses KOF (The King of Fighters) characters to demonstrate
// the !T type, anyerror!T, and error handling with try / catch / if
const std = @import("std");
// -----------------------------------------------
// Defining an error set
// -----------------------------------------------
// Enumerate errors that can occur during a match
const FightError = error{
UnknownFighter, // The fighter is not registered
NotEnoughEnergy, // Not enough energy
Disqualified, // Disqualified for a foul
};
// -----------------------------------------------
// Character struct
// -----------------------------------------------
const Fighter = struct {
name: []const u8,
energy: u32, // Remaining energy (moves cannot be used when it reaches 0)
is_banned: bool, // Disqualification flag
};
// -----------------------------------------------
// Functions that return an error union type
// -----------------------------------------------
// Returns the energy cost for a given move name.
// Returns an UnknownFighter error if the move name is not recognized.
fn getMoveCost(move: []const u8) FightError!u32 {
if (std.mem.eql(u8, move, "パワーウェイブ")) return 20;
if (std.mem.eql(u8, move, "バーンナックル")) return 30;
if (std.mem.eql(u8, move, "草薙流・八稚女")) return 50;
if (std.mem.eql(u8, move, "邪影炎")) return 60;
if (std.mem.eql(u8, move, "不知火流・龍炎舞")) return 40;
if (std.mem.eql(u8, move, "ガトリングアタック")) return 35;
return error.UnknownFighter; // Return an error if no move name matched
}
// Validates whether a fighter can use a move, and returns the energy after use.
// Returns the corresponding error if the fighter is disqualified or has insufficient energy.
fn useMove(fighter: Fighter, move: []const u8) FightError!u32 {
// Disqualified fighters cannot use moves
if (fighter.is_banned) return error.Disqualified;
// Use try to propagate any error from getMoveCost()
// If there is no error, cost is bound to the normal value
const cost = try getMoveCost(move);
// Return an error if the fighter does not have enough energy
if (fighter.energy < cost) return error.NotEnoughEnergy;
// Return the remaining energy
return fighter.energy - cost;
}
pub fn main() !void {
const stdout = std.io.getStdOut().writer();
// -----------------------------------------------
// Initialize character data
// -----------------------------------------------
const terry = Fighter{ .name = "テリー・ボガード", .energy = 80, .is_banned = false };
const kyo = Fighter{ .name = "草薙京", .energy = 100, .is_banned = false };
const iori = Fighter{ .name = "八神庵", .energy = 25, .is_banned = false }; // Low energy
const mai = Fighter{ .name = "不知火舞", .energy = 90, .is_banned = false };
const ralph = Fighter{ .name = "ラルフ・ジョーンズ", .energy = 70, .is_banned = true }; // Disqualified fighter
try stdout.print("=== KOF エラーユニオン型デモ ===\n\n", .{});
// -----------------------------------------------
// Normal case with try
// -----------------------------------------------
try stdout.print("--- try による技の使用(正常ケース)---\n", .{});
// try returns the normal value directly; on error, it propagates out of main()
const terry_energy = try useMove(terry, "バーンナックル");
try stdout.print("{s}: 技「バーンナックル」使用後のエネルギー = {d}\n", .{ terry.name, terry_energy });
const kyo_energy = try useMove(kyo, "草薙流・八稚女");
try stdout.print("{s}: 技「草薙流・八稚女」使用後のエネルギー = {d}\n", .{ kyo.name, kyo_energy });
const mai_energy = try useMove(mai, "不知火流・龍炎舞");
try stdout.print("{s}: 技「不知火流・龍炎舞」使用後のエネルギー = {d}\n\n", .{ mai.name, mai_energy });
// -----------------------------------------------
// Error handling with catch
// -----------------------------------------------
try stdout.print("--- catch によるエラー処理 ---\n", .{});
// Iori has only 25 energy, so he cannot use "邪影炎" (cost 60)
const iori_result = useMove(iori, "邪影炎") catch |err| blk: {
// Use a switch to inspect the error type and respond accordingly
switch (err) {
FightError.NotEnoughEnergy => {
try stdout.print("{s}: エネルギー不足で「邪影炎」は使えません!\n", .{iori.name});
},
FightError.Disqualified => {
try stdout.print("{s}: 失格のため技を使えません!\n", .{iori.name});
},
FightError.UnknownFighter => {
try stdout.print("不明な技です\n", .{});
},
}
// Return the current energy as the default value on error
break :blk iori.energy;
};
try stdout.print("{s}: 現在のエネルギー = {d}\n\n", .{ iori.name, iori_result });
// Ralph is disqualified and cannot use moves
const ralph_result = useMove(ralph, "ガトリングアタック") catch |err| blk: {
try stdout.print("{s}: エラー発生 — {s}\n", .{ ralph.name, @errorName(err) });
break :blk ralph.energy;
};
try stdout.print("{s}: 現在のエネルギー = {d}\n\n", .{ ralph.name, ralph_result });
// -----------------------------------------------
// Branching on an error union with if
// -----------------------------------------------
try stdout.print("--- if によるエラーユニオン分岐 ---\n", .{});
// Normal case: Terry's "パワーウェイブ"
if (useMove(terry, "パワーウェイブ")) |remaining| {
try stdout.print("{s}: 「パワーウェイブ」成功。残エネルギー = {d}\n", .{ terry.name, remaining });
} else |err| {
try stdout.print("{s}: 失敗 — {s}\n", .{ terry.name, @errorName(err) });
}
// Unknown move name: specifying a move that does not exist
if (useMove(terry, "スピニングバードキック")) |remaining| {
try stdout.print("{s}: 成功。残エネルギー = {d}\n", .{ terry.name, remaining });
} else |err| {
try stdout.print("{s}: 「スピニングバードキック」は使えません — エラー: {s}\n\n", .{ terry.name, @errorName(err) });
}
// -----------------------------------------------
// anyerror!T — when you do not want to restrict the error set
// -----------------------------------------------
try stdout.print("--- anyerror を使った汎用関数 ---\n", .{});
// describeFighter() uses anyerror!void because it deals with multiple error sets internally
try describeFighter(stdout, kyo, "草薙流・八稚女");
try describeFighter(stdout, iori, "邪影炎");
}
// anyerror!void — a generic function signature that can return any error
fn describeFighter(writer: anytype, fighter: Fighter, move: []const u8) anyerror!void {
try writer.print("[{s}] エネルギー: {d} / 技: {s} → ", .{ fighter.name, fighter.energy, move });
// Propagate errors from useMove() as-is (no cast needed because of anyerror)
const remaining = useMove(fighter, move) catch |err| {
try writer.print("失敗({s})\n", .{@errorName(err)});
return; // Return normally after handling the error
};
try writer.print("成功。残エネルギー: {d}\n", .{remaining});
}
zig run error_union.zig === KOF エラーユニオン型デモ === --- try による技の使用(正常ケース)--- テリー・ボガード: 技「バーンナックル」使用後のエネルギー = 50 草薙京: 技「草薙流・八稚女」使用後のエネルギー = 50 不知火舞: 技「不知火流・龍炎舞」使用後のエネルギー = 50 --- catch によるエラー処理 --- 八神庵: エネルギー不足で「邪影炎」は使えません! 八神庵: 現在のエネルギー = 25 ラルフ・ジョーンズ: エラー発生 — Disqualified ラルフ・ジョーンズ: 現在のエネルギー = 70 --- if によるエラーユニオン分岐 --- テリー・ボガード: 「パワーウェイブ」成功。残エネルギー = 60 テリー・ボガード: 「スピニングバードキック」は使えません — エラー: UnknownFighter --- anyerror を使った汎用関数 --- [草薙京] エネルギー: 100 / 技: 草薙流・八稚女 → 成功。残エネルギー: 50 [八神庵] エネルギー: 25 / 技: 邪影炎 → 失敗(NotEnoughEnergy)
Overview
The error union type !T (or ErrSet!T) is a type that tells the compiler a function may return either a normal value or an error. Silently ignoring an error causes a compile error, which prevents overlooked errors at the root level. Use try to propagate an error to the caller, or catch to handle it inline. The if (expr) |v| { } else |err| { } form lets you branch on both the normal value and the error simultaneously, making complex branching logic concise. anyerror!T is a generic type that does not restrict the error set, and is useful for functions that deal with multiple error sets or are combined with externally-provided function pointers. Zig also provides errdefer for logging error propagation — it registers a cleanup action that runs only when an error occurs as the scope exits. For the difference between error union types and optional types, see Optional Type ?T / null. For the full error-handling syntax, see Error Handling.
If you find any errors or copyright issues, please contact us.