Aside from being personally surprising, I suspect this would break code that was doing clever comptime assertions that might be skipped following a panic.
I don't know the exact rules, but note that these two examples are very different from the type system point of view:
panic!() diverges unconditionally; its return type is !.
Even though the condition is known to be false at compile time, the if statement is not in a compile-time eval context. From the type system POV the condition has the type bool which means it might be true or it might not, and there's no type in Rust that encodes "bool that's exactly true" (this is what dependent/refinement/flow/pattern types are about). It doesn't matter that the branch is "obviously" not taken (and the optimizer would trivially remove the whole statement), the compiler doesn't mix and match compile-time and run-time semantics like that.
On the other hand, this compiles as expected:
fn main() {
const {
if false {
panic!()
}
}
}
(OTOH if we start nesting consts[1], it stops compiling again. A const context nested inside a const context is a "deeper level" of constness in a sense. Meta-constant if you will.)
panic!() expands to a function with a ! return type. This is a compile-time property. The function guarantees to never return, and the compiler doesn't need to run it to know it.
As you have found, there is nothing in The Reference that states unreachable code in const "contexts" may not be evaluated at compilation time; thus I think you should submit a PR. If this were a less "precise" document, then I suppose I could forgive the omission; but I think it's appropriate for The Reference to be precise especially since the fix is so small. Specifically I'd change this (emphasis added):
In const contexts, these are the only allowed expressions, and are always evaluated at compile time.
to this:
In const contexts, these are the only allowed expressions; and if reachable, are always evaluated at compile time.
You could argue that your if false example is not "reachable", so perhaps being more specific like:
In const contexts, these are the only allowed expressions and are always evaluated at compile time unless unconditionally unreachable.
Tangentially related, you can denyunreachable_code to ensure there is no code after a panic or use an actual constant since items live for the entire scope they are declared in. For example, the below does not compile:
I think the lang and compiler teams don't want to make any promises that if not reachable, they won't be evaluated — the lack of evaluation is a compromise for compiler performance, not a desirable property. Also, "if reachable, are always evaluated at compile time" could be read as "if not reachable, may be evaluated at run time". There’s two different meanings of “always” here. What should be specified is:
If [one of these expressions] is evaluated, then that evaluation will occur at compile time.
My wording was not clear then. The "unless unconditionally unreachable" was not meant as "guaranteed to not be evaluated at compilation time" but instead that the guarantee of it being evaluated at compilation time no longer holds. The fact the compiler can evaluate non const contexts at compilation time is clearly true seeing how it's not difficult to get a let item to cause a compilation failure despite it not being a const context.
Indeed. I have been bitten by that in the past forcing me to either document that a panicmay occur or return an Option to guarantee that a panic won't occur.