What is the purpose of an empty switch statement?

I just notice this in the code for std.unicode.wtf16LeToWtf8:

pub fn wtf16LeToWtf8(wtf8: []u8, wtf16le: []const u16) usize {
    return utf16LeToUtf8Impl(wtf8, wtf16le, .can_encode_surrogate_half) catch |err| switch (err) {};
}

A switch statement must handle all possible errors the error set contains. The return type of utf16LeToUtf8Impl returns different error sets depending on some comptime parameters. This specific usage returns an empty error set. The switch is essentially verifying that the error set is empty. If an error is added, it would produce a compile error indicating that this piece of code needs to be fixed. It also informs the reader of the code that this is happening, and it’s not just discarding a possible error.

The reason for possibly returning an empty error union is to keep usage consistent across call sites. If there were no cases where it could return an error, then it wouldn’t return an error union.

4 Likes

This can also be done with just a try! which is terser, but would give a worse error message if an error were possible :frowning:

1 Like

Would it? Seems it would feel somewhat “normal”, though perhaps that’s the point - perhaps an abnormal error is wanted, to help achieves that goal: “It also informs the reader of the code that this is happening”.

It does seem like the empty switch is almost too clever by half. Is catch unreachable an option, if this is supposed to be impossible?

1 Like

unreachable on its own is allowed to pass to runtime as an optimisation hint, or safety check. Meaning it would compile regardless of if the path was reachable, meaning it will compile if it can return an error.

comptime unreachable would be better. I agree it communicates the intent better than an empty switch or try in an errorless function does. And its even a better error, since the error would communicate concisely and precisely what the problem is.

An empty switch would seem arcane to an unknowing reader. And its error of “all cases must be handled [list of cases to handle]” is nice in it’s intended context, does not communicate intent very well.

A try on the other hand, would give an error of “expected type usize found error{...}!usize”. Which, contrary to my earlier statement, actually communicates the problem and intent better. When I said it was a worse error I was thinking of those large error sets, sometimes with some @builtins included, that newer users struggle to decipher the first dozen times.
But reading the source try seems like it should not compile, so communicates intent poorly.