As an exercise for learning Zig, I solve Advent of Code problems. In a recent problem (Advent of Code 2022, day 2, both parts), I came across the following scenario:
const OpponentMove = enum {
ROCK,
PAPER,
SCISSORS,
pub fn fromU8(value: u8) !OpponentMove {
return switch (value) {
'A' => OpponentMove.ROCK,
'B' => OpponentMove.PAPER,
'C' => OpponentMove.SCISSORS,
else => Invalid.OpponentMove,
};
}
};
The problem required you to handle input for the classic game of Rock, Paper, Scissors. Naturally, this is a fallible operation because the input, while promised by AoC to be correct, could be invalid. At a glance, using error
s feels like the go-to choice for representing this problem. Not only can it be handled ergonomically by the caller, but it composes nicely with the caller’s return type. For example:
pub fn part1(strategyGuide: []const u8) Invalid!u32 {
// some code
const opponentMove = try OpponentMove.fromU8(gameLine[0]);
// some more code
While I’m quite confident that this code would pass the smell test and be considered idiomatic Zig, another option came to mind form the Rust world.
In a great video from the YouTuber Logan Smith, he shows that the Rust version of error
, Result
, are actually isomorphic to Option
if and only if there is exactly one variant of the Result
. This idea got me wondering:
When is it appropriate to use an optional instead of an error
?
Here’s what my previous code snippet would look like when implemented with an optional:
const OpponentMove = enum {
ROCK,
PAPER,
SCISSORS,
pub fn fromU8(value: u8) ?OpponentMove {
return switch (value) {
'A' => OpponentMove.ROCK,
'B' => OpponentMove.PAPER,
'C' => OpponentMove.SCISSORS,
else => null,
};
}
};
So far, it’s not to bad. Here’s what the call site could look like:
pub fn part1(strategyGuide: []const u8) Invalid!u32 {
// some code
const opponentMove = OpponentMove.fromU8(gameLine[0]) orelse return Invalid.OpponentMove;
// some more code
Hmm… this is where I feel like the code is less idiomatic. While this version of the function still gives the caller a way to propagate up the error
and thus not loose any information while handling errors, it feel quite a bit more verbose with the use of orelse
.
How does the community feel about this alternative?
Is it more “idiomatic” to use the try
key word? Or is my bias for playing code golf sneaking through?
I’d love to hear everyone’s thoughts and opinions on the subject