🎓 A1Learner
🔍
0 of 46 lessons completed

Zig Error Union Patterns

An error union is a value that is either a result or an error. This lesson shows the patterns for passing, catching, and branching on that value.

Chaining with try

Try chains helpers: an error in a helper returns from the caller at once.

const std = @import("std");
const LookupError = error{ NotFound, Empty };
fn lookup(ok: bool) LookupError!i32 {
    if (ok) return 42;
    return LookupError.NotFound;
}
fn doubled(ok: bool) LookupError!i32 {
    const v = try lookup(ok);
    return v * 2;
}
pub fn main() !void {
    const out = std.io.getStdOut().writer();
    try out.print("doubled: {d}\n", .{try doubled(true)});
    try out.print("fallback: {d}\n", .{lookup(false) catch 0});
}

Recovering in catch

A named catch reads the error, then the block gives a recovery value.

const std = @import("std");
const LookupError = error{ NotFound, Empty };
fn lookup(ok: bool) LookupError!i32 {
    if (ok) return 42;
    return LookupError.NotFound;
}
pub fn main() !void {
    const out = std.io.getStdOut().writer();
    const a = lookup(false) catch |err| blk: {
        try out.print("saw: {s}\n", .{@errorName(err)});
        break :blk -1;
    };
    try out.print("recovered: {d}\n", .{a});
    const b = lookup(true) catch |err| blk: {
        try out.print("saw: {s}\n", .{@errorName(err)});
        break :blk -1;
    };
    try out.print("kept: {d}\n", .{b});
}

Branching on the result

Payload capture splits the result case from the error case in one if.

const std = @import("std");
const LookupError = error{ NotFound, Empty };
fn lookup(which: u8) LookupError!i32 {
    if (which == 0) return 42;
    if (which == 1) return LookupError.Empty;
    return LookupError.NotFound;
}
fn describe(which: u8) []const u8 {
    if (lookup(which)) |v| {
        if (v == 42) return "found 42";
        return "found other";
    } else |err| {
        if (err == LookupError.NotFound) return "missing";
        return "empty input";
    }
}
pub fn main() !void {
    const out = std.io.getStdOut().writer();
    try out.print("{s}\n", .{describe(0)});
    try out.print("{s}\n", .{describe(1)});
    try out.print("{s}\n", .{describe(2)});
}

Example

const std = @import("std");
const ScoreError = error{ NoScore, BadScore };
fn score(ok: bool) ScoreError!i32 {
    if (ok) return 30;
    return ScoreError.NoScore;
}
fn bonus(ok: bool) ScoreError!i32 {
    const v = try score(ok);
    if (v < 0) return ScoreError.BadScore;
    return v + 10;
}
pub fn main() !void {
    const out = std.io.getStdOut().writer();
    if (bonus(true)) |v| {
        try out.print("total: {d}\n", .{v});
    } else |err| {
        try out.print("failed: {s}\n", .{@errorName(err)});
    }
    const fallback = bonus(false) catch 0;
    try out.print("fallback total: {d}\n", .{fallback});
    if (bonus(false)) |v| {
        try out.print("total: {d}\n", .{v});
    } else |err| {
        if (err == ScoreError.NoScore) {
            try out.print("no score recorded\n", .{});
        } else {
            try out.print("bad score\n", .{});
        }
    }
}

Key points

  • Error union: an error union is a value that is either a result or an error.
  • Try passes an error straight to the caller, so a helper can chain calls without handling each one.
  • Catch with a plain value gives a fallback result when the call fails.
  • Catch with a named error lets the handler read the error and choose a recovery value.
  • Payload capture: a payload capture is the if with a value capture and an else with an error capture, splitting the two cases.
  • A catch block must still produce a value of the result type, even when its body only prints and recovers.

bonus builds on score with try, so a missing score returns at once. The caller shows three handling styles: capture for the success case, a fallback value, and a branch on the exact error. Each style fits a different need, and none of them hides which error happened.

Try it Yourself

Edit the code, then press Run.

Running is simulated

Exercise

What does lookup(false) catch 0 give when lookup returns NotFound?