0

Zig's documentation shows different methods of error handling including bubbling the error value up the call stack, catching the error and using a default value, panicking, etc.

I'm trying to figure out how to retry functions which provide error values.

For example, in the below snippet from ziglearn, is there a way to retry the nextLine function in the event that a user enters greater than 100 characters?

fn nextLine(reader: anytype, buffer: []u8) !?[]const u8 {
    var line = (try reader.readUntilDelimiterOrEof(
        buffer,
        '\n',
    )) orelse return null;
    // trim annoying windows-only carriage return character
    if (@import("builtin").os.tag == .windows) {
        return std.mem.trimRight(u8, line, "\r");
    } else {
        return line;
    }
}

test "read until next line" {
    const stdout = std.io.getStdOut();
    const stdin = std.io.getStdIn();

    try stdout.writeAll(
        \\ Enter your name:
    );

    var buffer: [100]u8 = undefined;
    const input = (try nextLine(stdin.reader(), &buffer)).?;
    try stdout.writer().print(
        "Your name is: \"{s}\"\n",
        .{input},
    );
}

1 Answers1

3

This should do what you want.

const input = while (true) {
   const x = nextLine(stdin.reader(), &buffer) catch continue;
   break x;
} else unreachable; // (see comment) fallback value could be an empty string maybe?

To break it down:

  • instead of try, you can use catch to do something in the case of an error, and we're restarting the loop in this case.
  • while loops can also be used as expressions and you can break from them with a value. they also need an else branch, in case the loop ends without breaking away from it. in our case this is impossible since we're going to loop forever until nextLine suceeds, but if we had another exit condition (like a limit on the number of retries), then we would need to provide a "fallback" value, instead of unreachable.

You can also make it a one-liner:

const input = while (true) break nextLine(stdin.reader(), &buffer) catch continue else unreachable;

Hopefully the new self-hosted compiler will be able to pick up on the fact that the else branch is not necessary, since we're going to either break with a value loop forever.

kristoff
  • 456
  • 2
  • 3
  • 1
    Note that if `reader.readUntilDelimiterOrEof()` returns `error.StreamTooLong` because the user wrote more than 100 characters, then the loop should flush the input buffer before retrying or they'll get the rest of the first line instead of asking for the user to retry. – Wtrmute Jan 27 '22 at 18:00
  • 1
    @Wtrmute that is indeed the behavior I’m getting - sorry if it’s a silly question, but what function could I use to flush the input buffer? (And thank you Loris!) – Unsatisfied Zebra Jan 27 '22 at 22:08
  • 1
    I don't think there is a function you can use on the `Reader` type, and indeed also not on the `File` type either. You can write a small function like `fn flush(reader: anytype) @TypeOf(reader).Error!void { var scratch: [64]u8 = undefined; while(try reader.read(&scratch) != 0) {} }` and then modify Loris's line with `... &buffer) catch { flush(reader); continue; } else ...` – Wtrmute Jan 28 '22 at 01:47
  • @Wtrmute hmm.. I tried using that with this [snippet](https://pastebin.com/miQNVJ3r) but I get `./src/main.zig:5:41: error: integer value 0 cannot be coerced to type 'std.os.ReadError!usize' while (try reader.read(&scratch) != 0) {}`, which I wasn't expecting because the read call is part of a try expression.. any ideas? – Unsatisfied Zebra Jan 28 '22 at 07:41
  • @Wtrmute scratch that, I got it to run by changing it to `while (true) { if (reader.read(&scratch)) |val| { if (val != 0) break; } else |err| return err; }` – Unsatisfied Zebra Jan 28 '22 at 08:07
  • @Kristoff would you mind updating the answer to include Wtrmute's note? I have a [working example](https://pastebin.com/vQKSCJ7Y) of what I was trying to achieve for reference. I'll mark your answer as accepted after that. Thanks to both of you! – Unsatisfied Zebra Jan 28 '22 at 08:11