Language
日本語
English

Caution

JavaScript is disabled in your browser.
This site uses JavaScript for features such as search.
For the best experience, please enable JavaScript before browsing this site.

  1. Home
  2. Zig Dictionary
  3. Error-returning Functions (!T / try / catch)

Error-returning Functions (!T / try / catch)

In Zig, you can define a function that may return an error by specifying !T as the return type. At the call site, you choose whether to propagate the error automatically with try, or to handle it inline with catch. This page covers how to write functions that return !T, how to chain propagation with try, and common catch patterns.

Syntax

// -----------------------------------------------
// !T — Defining a function that may return an error
// -----------------------------------------------

fn functionName(arg: Type) !ReturnType {
    if (errorCondition) return error.ErrorName;  // Returns an error
    return normalValue;                           // Returns a normal value
}

// -----------------------------------------------
// try — Automatically propagates the error to the caller
// -----------------------------------------------

const value = try functionName(arg);
// If functionName() returns an error, the current function returns that error immediately
// On success, the value is unwrapped and bound to value

// Chaining try: multiple !T functions can be called in sequence
const a = try functionA(arg);
const b = try functionB(a);
const c = try functionC(b);

// -----------------------------------------------
// catch — Captures and handles the error inline
// -----------------------------------------------

// Receives the error value and branches on it
const value = functionName(arg) catch |err| {
    // err is bound to the error value
    // Log output, decide on a default value, etc.
    return defaultValue;
};

// When the error value is not needed, you can simply provide a default
const value = functionName(arg) catch defaultValue;

// -----------------------------------------------
// The calling function must also have !T in its signature to use try
// -----------------------------------------------

fn callerFunction() !void {
    const value = try errorReturningFunction();  // try is allowed because !void is declared
    _ = value;
}

Syntax reference

Syntax / MethodDescription
fn f() !T { }Defines a function that may return an error. The error set is inferred by the compiler.
return error.Name;Returns an error value. The error name can be any identifier you choose.
return normalValue;Returns a value on success. It is bound to the non-error side of the error union type.
try exprPropagates an error from the current function if one is returned. On success, unwraps and returns the value.
expr catch |err| { }Captures an error inline. The error value is bound to err.
expr catch defaultValueShorthand that returns a default value when an error occurs. Use this when you do not need to inspect the error value.
@errorName(err)Returns the name of an error value as a string slice. Useful for debug output.
errdefer stmt;Registers a cleanup statement that runs only when the scope exits due to an error.

Sample code

function_error.zig
// function_error.zig — Sample for !T-returning functions and try / catch
// Uses KOF (The King of Fighters) characters to demonstrate
// defining error-returning functions, chaining try, and recovering with catch

const std = @import("std");

// -----------------------------------------------
// Error set definition
// Lists the errors that can occur in match logic
// -----------------------------------------------

const MatchError = error{
    FighterNotFound,  // Fighter not found
    NotEnoughPower,   // Power gauge is insufficient
    Disqualified,     // Disqualified due to a foul
    InvalidRound,     // Invalid round number
};

// -----------------------------------------------
// Data structures
// -----------------------------------------------

const Fighter = struct {
    name: []const u8,
    power: u32,        // Current power gauge (0–100)
    is_banned: bool,   // Disqualification flag
};

// -----------------------------------------------
// Defining !T-returning functions
// -----------------------------------------------

// Validates the round number and returns a display name for the round.
// Round numbers outside 1–3 return an InvalidRound error.
fn getRoundName(round: u32) ![]const u8 {
    return switch (round) {
        1 => "Round 1",
        2 => "Round 2",
        3 => "Final Round",
        else => error.InvalidRound,
    };
}

// Checks whether a fighter is eligible to compete.
// Returns Disqualified for banned fighters, NotEnoughPower if the gauge is too low.
fn validateFighter(fighter: Fighter, cost: u32) !void {
    if (fighter.is_banned) {
        return error.Disqualified;
    }
    if (fighter.power < cost) {
        return error.NotEnoughPower;
    }
}

// Returns the power cost of a move.
// Returns FighterNotFound for unknown move names.
fn getMoveCost(move: []const u8) !u32 {
    if (std.mem.eql(u8, move, "Power Wave"))       return 20;
    if (std.mem.eql(u8, move, "Burn Knuckle"))     return 30;
    if (std.mem.eql(u8, move, "Kusanagi Flame"))   return 50;
    if (std.mem.eql(u8, move, "Dark Flame"))        return 60;
    if (std.mem.eql(u8, move, "Ryuenbu"))          return 40;
    if (std.mem.eql(u8, move, "Gatling Attack"))   return 35;
    return error.FighterNotFound;
}

// -----------------------------------------------
// Chaining try: calls multiple !T functions in sequence.
// If any call returns an error, it propagates immediately to the caller.
// -----------------------------------------------

// Returns the fighter's remaining power after using a move.
// Chains getRoundName, validateFighter, and getMoveCost with try.
fn executeMove(fighter: Fighter, move: []const u8, round: u32) !u32 {
    // If the round is invalid, InvalidRound propagates from here
    const round_name = try getRoundName(round);

    // If the move is unknown, FighterNotFound propagates from here
    const cost = try getMoveCost(move);

    // If the fighter cannot compete, Disqualified or NotEnoughPower propagates from here
    try validateFighter(fighter, cost);

    // errdefer: runs only when the scope exits due to an error
    errdefer {
        std.io.getStdOut().writer().print(
            "  [errdefer] An error occurred while executing {s}'s move\n", .{fighter.name}
        ) catch {};
    }

    std.io.getStdOut().writer().print(
        "  {s}: {s} uses \"{s}\" (cost {d})\n",
        .{ round_name, fighter.name, move, cost }
    ) catch {};

    // Returns the remaining power
    return fighter.power - cost;
}

pub fn main() !void {
    const stdout = std.io.getStdOut().writer();

    // Define KOF character data
    const terry = Fighter{ .name = "Terry Bogard",  .power = 80,  .is_banned = false };
    const kyo   = Fighter{ .name = "Kyo Kusanagi",  .power = 100, .is_banned = false };
    const iori  = Fighter{ .name = "Iori Yagami",   .power = 25,  .is_banned = false }; // Low power
    const mai   = Fighter{ .name = "Mai Shiranui",  .power = 90,  .is_banned = false };
    const ralph = Fighter{ .name = "Ralf Jones",    .power = 70,  .is_banned = true  }; // Disqualified

    try stdout.print("=== KOF Move Execution Demo (!T / try / catch) ===\n\n", .{});

    // -----------------------------------------------
    // Success cases with try
    // executeMove() chains three !T functions internally using try
    // -----------------------------------------------

    try stdout.print("--- Success cases with try ---\n", .{});

    // When execution succeeds, try unwraps and returns the value
    const terry_power = try executeMove(terry, "Burn Knuckle", 1);
    try stdout.print("  -> {s}: remaining power = {d}\n\n", .{ terry.name, terry_power });

    const kyo_power = try executeMove(kyo, "Kusanagi Flame", 2);
    try stdout.print("  -> {s}: remaining power = {d}\n\n", .{ kyo.name, kyo_power });

    const mai_power = try executeMove(mai, "Ryuenbu", 3);
    try stdout.print("  -> {s}: remaining power = {d}\n\n", .{ mai.name, mai_power });

    // -----------------------------------------------
    // catch |err| — Captures the error inline
    // -----------------------------------------------

    try stdout.print("--- Error handling with catch ---\n", .{});

    // Iori's power is 25, so "Dark Flame" (cost 60) cannot be used
    const iori_power = executeMove(iori, "Dark Flame", 1) catch |err| blk: {
        // Use switch to inspect the error type
        switch (err) {
            MatchError.NotEnoughPower => {
                try stdout.print("  {s}: Power gauge is insufficient ({s})\n",
                    .{ iori.name, @errorName(err) });
            },
            MatchError.Disqualified => {
                try stdout.print("  {s}: Cannot compete — disqualified\n", .{iori.name});
            },
            else => {
                try stdout.print("  Unexpected error: {s}\n", .{@errorName(err)});
            },
        }
        // Return the current power unchanged on error
        break :blk iori.power;
    };
    try stdout.print("  -> {s}: remaining power = {d} (unchanged)\n\n", .{ iori.name, iori_power });

    // Ralf has the disqualification flag set, so Disqualified is returned
    const ralph_power = executeMove(ralph, "Gatling Attack", 2) catch |err| blk: {
        try stdout.print("  {s}: error = {s}\n", .{ ralph.name, @errorName(err) });
        break :blk ralph.power;
    };
    try stdout.print("  -> {s}: remaining power = {d} (unchanged)\n\n", .{ ralph.name, ralph_power });

    // -----------------------------------------------
    // catch default value — Shorthand when the error value is not needed
    // -----------------------------------------------

    try stdout.print("--- catch default value (shorthand) ---\n", .{});

    // An unknown move name returns FighterNotFound, so the default value 0 is used
    const unknown_result = executeMove(terry, "Spinning Bird Kick", 1) catch 0;
    try stdout.print("  {s}: unknown move result = {d} (default value)\n\n", .{ terry.name, unknown_result });

    // -----------------------------------------------
    // Invalid round number — an error occurs inside getRoundName()
    // -----------------------------------------------

    try stdout.print("--- Invalid round number ---\n", .{});

    const bad_round = executeMove(kyo, "Kusanagi Flame", 99) catch |err| blk: {
        try stdout.print("  Round 99: error = {s}\n", .{@errorName(err)});
        break :blk kyo.power;
    };
    try stdout.print("  -> {s}: remaining power = {d} (unchanged)\n", .{ kyo.name, bad_round });
}
zig run function_error.zig
=== KOF Move Execution Demo (!T / try / catch) ===

--- Success cases with try ---
  Round 1: Terry Bogard uses "Burn Knuckle" (cost 30)
  -> Terry Bogard: remaining power = 50

  Round 2: Kyo Kusanagi uses "Kusanagi Flame" (cost 50)
  -> Kyo Kusanagi: remaining power = 50

  Final Round: Mai Shiranui uses "Ryuenbu" (cost 40)
  -> Mai Shiranui: remaining power = 50

--- Error handling with catch ---
  Iori Yagami: Power gauge is insufficient (NotEnoughPower)
  -> Iori Yagami: remaining power = 25 (unchanged)

  Ralf Jones: error = Disqualified
  -> Ralf Jones: remaining power = 70 (unchanged)

--- catch default value (shorthand) ---
  Terry Bogard: unknown move result = 0 (default value)

--- Invalid round number ---
  Round 99: error = InvalidRound
  -> Kyo Kusanagi: remaining power = 100 (unchanged)

Overview

In Zig, a function that may return an error declares its return type as !T. The ! is shorthand for an error union type; the compiler automatically infers the error set from the return error.Name; statements inside the function. At the call site, there are two ways to handle the result. Using try transparently propagates any error from the current function, and the success value is automatically unwrapped. This lets you chain calls like try functionA(), try functionB(), and try functionC() concisely. catch, on the other hand, is used when you want to handle the error at the call site. With catch |err| { } you can inspect the error value and branch on it; the shorthand catch defaultValue lets you specify a fallback value directly when an error occurs. errdefer registers a cleanup statement that runs only when the scope exits due to an error, making it useful for resource-release logging and similar tasks. For details on the error union type itself, see Error union type !T / anyerror. For the basics of defining functions, see Defining and calling functions.

If you find any errors or copyright issues, please .