🎓 A1Learner
🔍
0 of 55 lessons completed

Zig Handling Failures

Handling a failure means giving each error from a function its own response. A switch inside catch reads the error and picks the matching response.

Each error picks its own fallback number

calc can fail in two ways. The switch answers -1 for one error and -2 for the other.

const std = @import("std");

const CalcError = error{ DivideByZero, NegativeInput };

fn calc(a: i32, b: i32) CalcError!i32 {
    if (b == 0) {
        return error.DivideByZero;
    }
    if (a < 0) {
        return error.NegativeInput;
    }
    return @divTrunc(a, b);
}

pub fn main() !void {
    const out = std.io.getStdOut().writer();
    const first = calc(10, 0) catch |err| switch (err) {
        error.DivideByZero => -1,
        error.NegativeInput => -2,
    };
    const second = calc(-6, 2) catch |err| switch (err) {
        error.DivideByZero => -1,
        error.NegativeInput => -2,
    };
    try out.print("{d}\n", .{first});
    try out.print("{d}\n", .{second});
}

Each error picks its own message

label fails with NotFound for one input and TooBig for the other. Each print shows the message its error picked.

const std = @import("std");

const LabelError = error{ NotFound, TooBig };

fn label(n: i32) LabelError![]const u8 {
    if (n == 1) {
        return "one";
    }
    if (n > 9) {
        return error.TooBig;
    }
    return error.NotFound;
}

pub fn main() !void {
    const out = std.io.getStdOut().writer();
    const small = label(5) catch |err| switch (err) {
        error.NotFound => "missing",
        error.TooBig => "too big",
    };
    const big = label(50) catch |err| switch (err) {
        error.NotFound => "missing",
        error.TooBig => "too big",
    };
    try out.print("{s}\n", .{small});
    try out.print("{s}\n", .{big});
}

Else covers the errors without a prong

The switch names two errors and lets else handle the rest. The failing call returns Locked, so else prints its name.

const std = @import("std");

const DoorError = error{ Locked, Jammed, NoKey };

fn openDoor(code: i32) DoorError![]const u8 {
    if (code == 1) {
        return "opened";
    }
    if (code == 2) {
        return error.Jammed;
    }
    if (code == 3) {
        return error.NoKey;
    }
    return error.Locked;
}

pub fn main() !void {
    const out = std.io.getStdOut().writer();
    const word = openDoor(9) catch |err| switch (err) {
        error.Jammed => "try again",
        error.NoKey => "find the key",
        else => @errorName(err),
    };
    try out.print("{s}\n", .{word});
}

Example

const std = @import("std");

const BankError = error{ NotEnough, BadAmount };

fn withdraw(balance: i32, amount: i32) BankError!i32 {
    if (amount <= 0) {
        return error.BadAmount;
    }
    if (amount > balance) {
        return error.NotEnough;
    }
    return balance - amount;
}

pub fn main() !void {
    const out = std.io.getStdOut().writer();
    const left = try withdraw(100, 30);
    const too_much = withdraw(100, 150) catch |err| switch (err) {
        error.NotEnough => -1,
        error.BadAmount => -2,
    };
    const zero = withdraw(100, 0) catch |err| switch (err) {
        error.NotEnough => -1,
        error.BadAmount => -2,
    };
    try out.print("{d}\n", .{left});
    try out.print("{d}\n", .{too_much});
    try out.print("{d}\n", .{zero});
}

Key points

  • Catch switch: expr catch |err| switch (err) { ... } picks a fallback per error.
  • Prongs: each prong names one error and gives the value to use for it.
  • Capture first: the |err| capture is what hands the error to the switch.
  • Else prong: an else branch covers every error without its own prong.
  • One type: every prong must produce the same type as the success value.

In this example, the first withdrawal succeeds and try unwraps the 70 left in the account. The second asks for more than the balance, so the switch maps NotEnough to -1. The third asks for zero, so the switch maps BadAmount to -2.

Note

plain try fits when one response suits every error, while the switch fits when errors need different responses.

Try it Yourself

Edit the code, then press Run.

Running is simulated

Exercise

A catch switch has prongs for error.Empty and error.Full, plus an else prong. The call fails with error.Locked. Which prong runs?