🎓 A1Learner
🔍
0 of 55 lessons completed

Zig Try and Catch

Try and catch are the keywords that handle an error union where it is called. Try unwraps the value or sends the error to the caller, while catch swaps the error for a fallback.

Try unwraps a good value

The division succeeds, so try takes the number out of the union. The print shows the unwrapped number.

const std = @import("std");

const MathError = error{DivideByZero};

fn divide(a: i32, b: i32) MathError!i32 {
    if (b == 0) {
        return error.DivideByZero;
    }
    return @divTrunc(a, b);
}

pub fn main() !void {
    const out = std.io.getStdOut().writer();
    const answer = try divide(20, 4);
    try out.print("{d}\n", .{answer});
}

Catch swaps an error for a fallback

This division fails, so catch throws the error away. The fallback 0 prints instead.

const std = @import("std");

const MathError = error{DivideByZero};

fn divide(a: i32, b: i32) MathError!i32 {
    if (b == 0) {
        return error.DivideByZero;
    }
    return @divTrunc(a, b);
}

pub fn main() !void {
    const out = std.io.getStdOut().writer();
    const answer = divide(20, 0) catch 0;
    try out.print("{d}\n", .{answer});
}

Catch can name the error it caught

The capture |err| hands the error to the fallback. Here the fallback is the error name itself.

const std = @import("std");

const LookupError = error{NotFound};

fn wordFor(n: i32) LookupError![]const u8 {
    if (n == 1) {
        return "one";
    }
    return error.NotFound;
}

pub fn main() !void {
    const out = std.io.getStdOut().writer();
    const word = wordFor(9) catch |err| @errorName(err);
    try out.print("{s}\n", .{word});
}

Example

const std = @import("std");

const MathError = error{DivideByZero};

fn divide(a: i32, b: i32) MathError!i32 {
    if (b == 0) {
        return error.DivideByZero;
    }
    return @divTrunc(a, b);
}

pub fn main() !void {
    const out = std.io.getStdOut().writer();
    const good = try divide(36, 6);
    const bad = divide(36, 0) catch -1;
    try out.print("{d}\n", .{good});
    try out.print("{d}\n", .{bad});
}

Key points

  • Try: try expr unwraps the value when the call succeeds.
  • Propagation: try expr returns the error to the caller at once when the call fails.
  • Caller rule: try only works inside a function that can itself return an error.
  • Catch: expr catch fallback uses the fallback value when the call fails.
  • Capture: catch |err| names the error so the fallback can use it.
  • Error name: @errorName(err) turns a captured error into its bare name.

In this example, the first call divides 36 by 6 and try unwraps the result 6. The second call divides by zero and fails. Catch replaces that error with the fallback -1, which prints on the second line.

Note

main is written as pub fn main() !void in this course, so try can pass an error out of main.

Try it Yourself

Edit the code, then press Run.

Running is simulated

Exercise

In main, divide(8, 0) returns error.DivideByZero. What does the line const v = divide(8, 0) catch 99; store in v?