Implement a basic try-catch block

Hi!

I’m in a situation in which a basic try-catch block would be very handy.

I want to check that the user entered 2 arguments via the command line, and show a message and return error otherwise.

In C, this would be very easy, I would do:

int main(int argc, char *argv[]) {
  if (argc != 3) {
    printf("Usage: %s <source> <dest>\n", argv[0]);
    return 1;
  }
}

I’m stuggling to do the same in Zig.

Right now I have the following:

pub fn main() !void {
    var args = std.process.args();

    const bin: []const u8 = args.next().?;
    const source_path: []const u8 = args.next() orelse return error.InvalidArguments;
    const dest_path: []const u8 = args.next() orelse return error.InvalidArguments;
    if (args.skip()) return error.InvalidArguments;

    print("bin: {s}\n", .{bin});
    print("source: {s}\n", .{source_path});
    print("dest: {s}\n", .{dest_path});
}

I would like to be able print a message to the user and return an error if any of the lines above with the return error.InvalidArguments fail.

How do I do that in Zig?

You can use a labeled block together with break :label error.Error to get something similar to a try-catch block:

const std = @import("std");
const print = std.debug.print;

pub fn main() !void {
    // Side note: 'std.process.args' is not portable and won't work on Windows
    var args = try std.process.argsWithAllocator(std.heap.page_allocator);
    defer args.deinit();

    const bin: []const u8 = args.next().?;
    const source_path: []const u8, const dest_path: []const u8 = read_args: {
        const source = args.next() orelse break :read_args error.InvalidArguments;
        const dest = args.next() orelse break :read_args error.InvalidArguments;
        if (args.skip()) break :read_args error.InvalidArguments;
        break :read_args .{ source, dest };
    } catch |err| {
        print("Usage: {s} <source> <dest>\n", .{bin});
        return err;
    };

    print("bin: {s}\n", .{bin});
    print("source: {s}\n", .{source_path});
    print("dest: {s}\n", .{dest_path});
}

This example uses tuple destructuring which I believe is only available on the master branch of the compiler. If you prefer and you could also declare mutable variables outside of the block and assign them inside:

const bin: []const u8 = args.next().?;
var source_path: []const u8 = undefined;
var dest_path: []const u8 = undefined;
(read_args: {
    source_path = args.next() orelse break :read_args error.InvalidArguments;
    dest_path = args.next() orelse break :read_args error.InvalidArguments;
    if (args.skip()) break :read_args error.InvalidArguments;
} catch |err| {
    print("Usage: {s} <source> <dest>\n", .{bin});
    return err;
});

Here the parentheses are necessary for the compiler to parse the block as an expression and not a statement.

7 Likes

That’s exactly what I was looking for! Thank you very much!