If we do not require a proof that generic constants are evaluatable we end up with errors during monomorphization, which is bad™ as these don't get detected by cargo check
.
TODO: Elaborate on the difference between evaluatable { N - 1 }
and is_true { N > 0 }
.
It is not possible to rely on conditions to prevent errors during monomorphization by themselves, as it not possible for the compiler to prove that a condition completely covers the cases where some other constant panics. This is related to silent CTFE errors.
Having a lint which suggests adding conditions for each potentially panicking constant is also very difficult, as the compiler does not look into functions for this. We also again have the issue that having the compiler figure out with which inputs something causes a panic is impossible.
evaluatable
A term is evaluatable
if it does not panic or never exit. For example, <const N:usize> ... evaluatable { N }
is satisfied, since N is known to be a usize
, and is unmodified. As compared to that evaluatable { loop {} }
is not satisfied, since it is of type !
. There can be cases where it is not immediately clear that a function is not evaluatable. For example, evaluatable { N - 1 }
may be unevaluatable since 0-1
will overflow and panic. Thus, we enforce that the given expression is well-defined and will not panic. As long as the expression does not evaluate to the !
type, it will compile
is_true
The condition is_true
is more explicit than evaluatable. It enforces that a certain condition evaluates to a boolean, such as is_true { N > 0 }
. The issue with
this definition is that is_true
may also evaluate to !
, and it's not clear how to handle that case. For example, consider is_true { N - 1 > 0 }
. What should the behavior be when N=0
, since the expression will panic? From this, an intuitive definition is that is_true
is actually a superset of evaluatable
. Specifically, is_true
is evaluatable { <expr> } && <expr> == true
. It is possible to force <expr>
to never panic, for example by switching is_true { N -1 > 0 } => is_true { N > 1 }
. In some cases though, it may not be clear how to do this conversions.
evaluatable
vs is_true
TODO I think that evaluatable
to an end-user is kind of opaque? is_true
is very clear that an expression must evaluate to true, and doesnt' just panic, but maybe that is just me bike-shedding on evaluatable
being hard to understand. I also wonder whether certain expressions are representable in is_true
more easily, such as N == 3, ID > 299 && ID < 500
and other things.
In Haskell, certain type-level errors can be deferred until run-time. It may be useful to provide such functionality. This would likely imply replacing expressions that are do not satisfy evaluatable
with a panic. This seems bad in some cases, since a programmer may accidentally hit a sharp edge in their code where it is not clear that their const expression would cause a panic. Alternatively, it may make the common case much more ergonomic. If we were to include this, it might be possible to use warnings, or downgradable errors.