Description
What is this
This is a design document for const generics. Any discussions about its content should be on zulip. The conclusions of these discussions should then be edited back into this issue. Please do not post any comments directly in this issue.
Content
If we do not require a proof that generic constants evaluate successfully, we end up with errors during monomorphization. These are bad™ as these don't get detected by cargo check
.
Evaluatable bounds vs conditions
There are generally two possible ways to add bounds which rely on CTFE. We can require that some expression evaluates to true
(a condition). Alternatively we can require that some expression successfully evaluates (an evaluatable bound).
It is still unclear which kind of bound we should use and whether evaluatable bounds should be required. By requiring evaluatable bounds for all constants used in the type system, we remove a major future source of errors during monomorphization.
Comparing usability of evaluatable bounds and conditions
Depending on why a bound is needed, it can be clearer to express it as either an evaluatable bound or a condition.
If we require some generic parameter to be prime, is_true { is_prime(N) }
is simpler than
evaluatable { assert!(is_prime(N)) }
.
However, if we don't want some expression like N / 4 - 2
to panic, adding evaluatable { N / 4 - 2 }
is easier to add and less prone to mistakes than adding is_true { N > 7 }
. This does however mean that we now require users to understand the evaluatable bound. For users of a library conditions are probably always easier to understand than evaluatable bounds.
Conditions cannot prevent all monomorphization errors by themselves
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 have the issue that const evaluation is Turing-complete. That means that it is impossible for the compiler figure out which inputs may cause a panic.
Evaluatable bounds for constants used outside of the type system
We already allow the use of generic associated constants in expressions, which causes errors during monomorphization.
struct HasAssoc<const N: usize>;
impl<const N: usize> HasAssoc<N> {
const MINUS_ONE: usize = N - 1;
}
fn minus_one<const N: usize>() -> usize {
HasAssoc::<N>::MINUS_ONE
}
fn passes_cargo_check() {
minus_one::<0>();
}
fn main() {
// causes an error during monomorphization.
passes_cargo_check();
}
It isn't trivially obvious whether there is a meaningful difference between constants used in the type system and constants only used in expressions.