Then, before building for production, all I need to do is a quick find or grep for unreachable and then decide how to handle the errors.
This means you did not design for handling errors and you’re instead treating them as exceptional cases which is another issue in itself.
they give me the impression of a highly restricted use of this part of the language, pretty much removing its usefulness in the develop → test → debug phase.
The issue is that that’s exactly the case with unreachable as it’s asserting that the path may not be reached allowing the compiler to play tetris if it ever happens. unreachable for switch prongs is completely fine as long as you guarantee they really are unreachable by either preconditions for the function or checks above the switch. Other uses of unreachable within switch is a wrong use of the tool (this is an absolute due to what it communicates to the compiler). Using it to “discard” was the issue as unreachable is taken as a guarantee that it won’t happen thus if mayFail() actually fails then it could start playing tetris, fire all missles, write a song, and so on as it’s undefined what happens after. Safety checked undefined behaviour doesn’t change that it’s undefined behaviour even if it may feel convenient when prototyping.
pub fn func(min: u8, max: u8) u8 {
// same as `if (min > max) unreachable;`
std.debug.assert(min <= max);
var byte: u8 = 0;
while (byte <= max) : (byte += 1) {
if (byte >= min) {
return byte;
}
}
// explicit `unreachable` is required to avoid the error:
// "function with non-void return type 'u8' implicitly returns"
unreachable;
}
I’m not sure if I think this is terribly bad, I actually think it illustrates the idea of unreachable being used to assert invariants the compiler can’t/doesn’t keep track of. However it is a recursive use of unreachable. The Second unreachable is only true because there is a previous case (in the assert) where a state was asserted as unreachable. In unsafe release modes, your second assertion would not necessarily be true, and open you up to UB. Would this example be as clear, illustrate the idea, and not contain a hidden problem:
pub fn func(min: u8, max: u8) u8 {
// same as `if (min > max) unreachable;`
if (max < min) {
@panic("Min can not be greater than max")
};
var byte: u8 = 0;
while (byte <= max) : (byte += 1) {
if (byte >= min) {
return byte;
}
}
// explicit `unreachable` is required to avoid the error:
// "function with non-void return type 'u8' implicitly returns"
unreachable;
}
The mayFail() catch unreachable text is wrong no matter what the intro says. Including it will have beginners go down the wrong path regardless of the intro as it doesn’t make it any more “correct”. unreachable being reached is illegal behaviour and not an alternative to @panic("this should never fail").
I don’t think communication is working here so I’ll just give up.
Can be used to indicate that certain, or remaining, switch cases cannot happen.
I think that solves the discussion about the switch cases?
The programmer states/promises/asserts to the compiler that those cases can’t happen.
“be handled” seems like it implies that they could happen just can’t be handled, but in that case you would use @panic.
The Language Reference has an example when talking about inline. Maybe it can be used instead.
fn withFor(any: AnySlice) usize {
const Tag = @typeInfo(AnySlice).Union.tag_type.?;
inline for (@typeInfo(Tag).Enum.fields) |field| {
// With `inline for` the function gets generated as
// a series of `if` statements relying on the optimizer
// to convert it to a switch.
if (field.value == @intFromEnum(any)) {
return @field(any, field.name).len;
}
}
// When using `inline for` the compiler doesn't know that every
// possible case has been handled requiring an explicit `unreachable`.
unreachable;
}
Last note, catch unreachable can be used when you statically know that an error will not occur for that call. That is, mayFail() catch unreachable cannot fail due to an implementation detail you know about but cannot communicate to the compiler. It’s not ideal (better to add a function which asserts success) but it’s a valid case.
Think of list.appendAssumeCapacity() which asserts a specific thing rather than “cannot fail”. Intent is clear and less likely to be violated over future updates to the codebase.