Error Handling

Did you see the !voidreturn type of the mainfunction from earlier? That is Zig's way of indicating that the mainfunction could possibly return an error. Error handling is a feature that was designed carefully in Zig, and worth exploring before we go further.

Defining an error

An error is defined in a similar way to an enum. You can also use ||to combine different error types together. Here are some errors from the standard library.

// in std/mem/Allocator.zig
pub const Error = error{
    OutOfMemory,
};

// in std/io.zig
pub const NoEofError = ReadError || error{
    EndOfStream,
};

// in std/dynamic_library.zig
const ElfDynLibError = error{
    FileTooBig,
    NotElfFile,
    NotDynamicLibrary,
    MissingDynamicLinkingInformation,
    ElfStringSectionNotFound,
    ElfSymSectionNotFound,
    ElfHashTableNotFound,
} || posix.OpenError || posix.MMapError;

Returning errors

After defining your error, you have to indicate that a function you've written can possibly return an error. This is done by placing the error name, then a !, then the actual return type.

In the following example, the function myFunctioncan only return MyError. There is no other possible error type. On the other hand, myOtherFunctioncould possibly return other error types — its error type is inferred from the function body.

const MyError = error{
    FooReason,
    BarReason,
    AnotherReason,
};

pub fn myFunction(x: i32) MyError!i32 {
    if (x < 43) {
        // Simply return the error if you encounter an error condition, instead
        // of returning the result.
        return MyError.FooReason;
    } else if (x > 43) {
        return MyError.BarReason;
    } else {
        return 33;
    }
}

// Here the error type is inferred, instead of being explicitly defined.
pub fn myOtherFunction(x: i32) !i32 {
    if (x < 99) {
        return MyError.AnotherReason;
    } else if (x > 99) {
        return MyError.BarReason;
    } else {
        return 22;
    }
}

Zig also has an anyerrortype, which represents the union of all the error types across the entire program. So pretty much any error in the program.

Handling errors

You can either use tryto propagate errors from functions that you call, or catchto handle the errors at the call site. In Zig, errors must be handled (eventually), otherwise you'll get a compiler error.

pub fn main() !void {
    // This will propagate the error up to the caller of this function.
    // In the case of the `main` function, it would end the program.
    const my_function_result = try myFunction(43);
    std.debug.print("The result of myFunction is: {}\n", .{my_function_result});

    // Instead of propagating the error, you can also handle it.
    const my_other_function_result = myOtherFunction(43) catch |err| {
        switch (err) {
            MyError.FooReason => std.debug.print("FooReason\n", .{}),
            MyError.BarReason => std.debug.print("BarReason\n", .{}),
            MyError.AnotherReason => std.debug.print("AnotherReason\n", .{}),
        }
        // Return early from the main function.
        return;
    };
    std.debug.print("The result of myOtherFunction is: {}\n", .{my_other_function_result});
}

Last updated