error-handlingzig

Get environment variable with default


I am on Zig 0.14.dev and I want to get an environment variable with some default. The docs say that if the variable is not present an error is raised. Now, if any other error is raised (e.g. OutOfMemory), I want it to propagate.

const std = @import("std");

pub fn main() !void {
    var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
    defer arena.deinit();
    const allocator = arena.allocator();

    const db_path = std.process.getEnvVarOwned(allocator, "DB_PATH") catch |err| {
        if (err == std.process.GetEnvVarOwnedError.EnvironmentVariableNotFound) {
            return "/path";
        } else {
            return err;
        }
    };

    std.debug.print("path: {s}\n", .{db_path});
}

That gives me this error though:

src/main.zig:11:20: error: expected type '@typeInfo(@typeInfo(@TypeOf(main.main)).@"fn".return_type.?).error_union.error_set!void', found '*const [5:0]u8'
            return "/path";
                   ^~~~~~~
src/main.zig:4:16: note: function return type declared here
pub fn main() !void {
               ^~~~

How can I do this correctly?


Solution

  • If you want to "return" a value from catch to the outer scope still within the function, you can't use return; it will return from the function. You get an error because the function can't return a string as its return type is !void.

    You can use a labeled block with break:

    const db_path = ... catch |err| blk: {
        if (...) {
            break :blk "/path";
        } else {
            return err;
        }
    };
    

    The name of the label is arbitrary, blk is often used by convention.

    Note that you do not have to fully qualify the name of the error, as it is added to the global error set and you can refer it by error.EnvironmentVariableNotFound.

    Using switch instead of if also can make the code more compact:

    const db_path = ... catch |err| blk: {
        switch (err) {
            error.EnvironmentVariableNotFound => break :blk "/path",
            else => return err,
        }
    };
    

    Or instead of a labeled block, you can use if, or switch, respectively, as an expression. Then you must not use {} for grouping (they make a block, besides being part of the switch syntax), you can use () (or nothing) instead:

    const db_path = ... catch |err| (
        if (err == err.EnvironmentVariableNotFound) "/path"
        else return err
    );
    
    const db_path = ... catch |err| (
        switch (err) {
            error.EnvironmentVariableNotFound => "/path",
            else => return err,
        }
    );
    
    const db_path = ... catch |err| switch (err) {
        error.EnvironmentVariableNotFound => "/path",
        else => return err,
    };