Skip to content

Zig Error Handling — Error Sets, Error Union Types, try, and catch

DodaTech Updated 2026-06-29 3 min read

In this tutorial, you will learn about Zig Error Handling. We cover key concepts, practical examples, and best practices to help you master this topic.

Zig's error handling is explicit, zero-cost, and integrated into the type system. Unlike exceptions (hidden control flow) or return codes (can be ignored), Zig's error union types make every possible failure visible at the call site.

In this tutorial, you'll learn Zig's error handling philosophy and syntax. Zig treats errors as values — no try/catch overhead, no unwinding, no hidden failure paths.

What You'll Learn

  • Error sets (declaring possible errors)
  • Error union types (ReturnType!ErrorType)
  • try: propagate errors
  • catch: handle errors
  • Switch on errors
  • Error return traces

Error Sets

const std = @import("std");

// Declare an error set
const FileError = error{
    NotFound,
    PermissionDenied,
    InvalidPath,
    Unknown,
};

// Functions return error unions
fn readFile(path: []const u8) FileError![]const u8 {
    if (std.mem.eql(u8, path, "")) return FileError.InvalidPath;
    // ... read file logic
    return "file contents";
}

Error Union Types

// The exclamation mark means error union
// FileError![]const u8 = "either a FileError or a []const u8"

// Caller must handle errors
pub fn main() void {
    // Compile error: error union ignored!
    // readFile("test.txt");

    // Solution: try or catch
}

// Returning errors
fn divide(a: i32, b: i32) !i32 {
    if (b == 0) return error.DivisionByZero;
    return a / b;
}

try — Propagate Errors

// try propagates the error to the caller
fn processFile(path: []const u8) !void {
    // If readFile fails, this function returns the error
    const content = try readFile(path);
    std.debug.print("Content: {s}
", .{content});
}

// Works with any error type
fn fetchData(url: []const u8) ![]const u8 {
    const response = try httpGet(url);
    return response.body;
}

catch — Handle Errors

// catch provides a default value
const content = readFile("test.txt") catch |err| {
    std.debug.print("Error: {}
", .{err});
    return;  // Exit or provide default
};

// Provide default value
const score = parseScore("not-a-number") catch 0;

Switch on Errors

fn handleFile(path: []const u8) void {
    const result = readFile(path);
    switch (result) {
        // Success case
        inline else => |content| {
            std.debug.print("Content: {s}
", .{content});
        },
        // Error case
        else => |err| {
            switch (err) {
                error.NotFound => std.debug.print("File not found
", .{}),
                error.PermissionDenied => std.debug.print("Access denied
", .{}),
                else => std.debug.print("Unknown error: {}
", .{err}),
            }
        },
    }
}

Error Return Traces

// Zig tracks error return traces (not stack traces) at compile time
// Use @errorReturnTrace() to access at runtime

fn deep() !void {
    return error.Fail;
}

fn middle() !void {
    try deep();
}

fn outer() !void {
    try middle();
}

// When called:
// outer → middle → deep → error.Fail
// Zig records the error return chain with zero runtime cost (if not captured)

Optional Types

// ?T means T or null (similar to error unions but for null)
fn findIndex(arr: []const i32, target: i32) ?usize {
    for (arr, 0..) |val, idx| {
        if (val == target) return idx;
    }
    return null;
}

// Using optionals
const idx = findIndex(&[_]i32{ 10, 20, 30 }, 20);
if (idx) |i| {
    std.debug.print("Found at {}
", .{i});
} else {
    std.debug.print("Not found
", .{});
}

// Or (provide default)
const i = idx orelse 0;

Practice Questions

  1. Declare an error set for a database module (ConnectionFailed, QueryError, Timeout, InvalidInput).

  2. Write a function that reads a config file and returns a parsed config, propagating any errors.

  3. Use catch to provide a default configuration when the config file is missing.

  4. Write a function that tries multiple fallback URLs and returns the first successful response.

  5. Combine optionals and error unions in a single function signature.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro