#[warn(unreachable_code)] warning on unreachable!()

There are some cases where I want to make sure that something is unreachable. unreachable!() is an ergonomic way to do this, but sometimes the compiler is a little too smart and already detects that the unreachable!() statement is never reachable. Then it warns about it:

|  (...)
| |_..._- any code following this `match` expression is unreachable, as all arms diverge
|        unreachable!();
|        ^^^^^^^^^^^^^^ unreachable statement
|

I feel like #[warn(unreachable_code)] should not warn about an unreachable statement that is specifically unreachable!().

whoops "unreachable_code" warns even on "unreachable!" itself · Issue #107953 · rust-lang/rust · GitHub sry thought i looked first

Can you say why you want to write it even if it's not needed?

Would it be fine for you to put #[expect(unreachable_code)] on the arm to specifically say that you intended for it to be this way? That way you'll get a warning when it isn't unreachable any more, too.

1 Like

Unless it depends on optimization level (does it?). It still means the compiler should be suggesting that in place of unreachable!() though, because unreachable!() also means "I intend for this to be unreachable", just at a different level.

But there's a huge difference between "I intend this to be unreachable because of algorithmic invariants" where there's not type-level proof that it's unreachable vs things like "I've covered all the cases in this enum so this arm is literally unreachable".

unreachable!() for the former is great. As is the warning so that if you think you're in the former but you're actually in the latter, you can just remove the truly-impossible-to-reach part.

(The only reason to use the expect is if you're intentionally trying to insulate yourself from particular semver-breaking changes.)

2 Likes

I'm not saying it's not better. I'm saying at present it doesn't seem very discoverable. So either the warning needs to change to suggest the better version, or the less better version should to be allowed.

Whether #[expect(unreachable_code)] is actually something a warning could reasonably suggest was where I had a question.

  • Does that expect depend on optimizations? If it does then unreachable!() is fine.
  • Is that expect robust against small code changes?
    • Yes: it sounds just better that's great.
    • No
      • Is that good? It warns users that code properties they expected no longer hold
      • Or bad? It means that a typical edit/compile workflow will pointlessly toggle back and forth between #[expect(unreachable_code)] and unreachable!().

Personally I have used expect very rarely, but like many things I would probably use it more if the compiler were suggesting it.

Removing the truly-impossible-to-reach part changes what happens in the case of future changes introducing a bug that causes the impossible-to-reach part to be reachable. In the case of a statically unreachable unreachable!(), removing it changes the behavior from a panic (loud signal of a problem) to continuing execution (no signal).

Ideally, there would always be a way to express the control flow such that accidentally introducing such a continuation path is a type error. For example, suppose I have a thread that is not intended to ever terminate; I could write a return type

std::thread::spawn(|| -> ! {
    loop {
        // ...
    }
});

to express that if any break is ever added to that loop, there is a bug. However, not every situation admits such an easy static guarantee — wait, is that true? Actually, it might not be, now that I think about it. It might be that, in the cases where unreachable_code would fire, you can always get an ! or other type error somehow. I am not sure.

The compiler should only say code is unreachable when it's proven impossible by the type system, yeah. (If there's any case where it doesn't, that's probably a bug - after all, even in the else branch of if true, you need to produce a value to satisfy the type system!)

Putting unreachable!() in such places actually downgrades the error: if that path becomes reachable, putting nothing there causes a compile error, while putting unreachable!() causes a runtime panic.

With that in mind, I'd prefer if the warning stayed - it's actually a good suggestion, cause putting unreachable!() somewhere that's proven unreachable is a semver footgun.

1 Like

Here is how you can check at compile time that the match diverges in all branches:

enum Impossible {}

let _ : Impossible = match x {
    false => return,
    true => return,
};
4 Likes