Description
Currently there's a suboptimal interaction between const asserts and const generics.
A more general function that is valid for all N cannot call a function that asserts at compile time that N fulfills some conditions even if the call is on a branch that's dead for the invalid N.
I.e. this does not compile:
#![feature(inline_const)]
// method in std
fn method_with_precondition<const N: usize>() {
const { assert!(N > 0) };
}
// user method, no way to opt out of asserts for N = 0
fn generic_caller_method<const N: usize>() -> fn() {
if N > 0 {
panic_on_zero::<N>
} else {
|| {} // fallback
}
}
fn main() {
let _fun = foo::<0>();
}
error[E0080]: evaluation of `panic_on_zero::<0>::{constant#0}` failed
--> src/main.rs:5:13
|
5 | const { assert!(N > 0) };
| ^^^^^^^^^^^^^^ the evaluated program panicked at 'assertion failed: N > 0', src/main.rs:5:13
|
= note: this error originates in the macro `assert` (in Nightly builds, run with -Z macro-backtrace for more info)
This blocks / requires unpleasant choices in several T-libs features that want to check for N != 0
or similar constraints:
- Tracking Issue for
Iterator::array_chunks
rust#100450 - Tracking Issue for slice::array_chunks rust#74985
- Tracking Issue for slice::array_windows rust#75027
- Tracking Issue for split_array rust#90091 - although this one needs generic_const_exprs before we can even worry about callers
- Tracking Issue for
Iterator::map_windows
(featureiter_map_windows
) rust#87155
If const-eval in the dead branches is expensive it might also help compile perf.
There are efforts (rust-lang/rust#99682) to move monomorphization-time errors to check time and also apply those checks to dead code (rust-lang/rust#112879), which will make it even more difficult to discharge compile-time obligations imposed by callees.
Currently the language does not seem to have any way to select different code paths in generic contexts without also instantiating those paths. Even generic_const_exprs
does not offer this unless it gets extended rust-lang/project-const-generics#26
Therefore it would be useful if we could write the case above as
fn foo<const N: usize>() -> fn() {
const if N > 0 {
panic_on_zero::<N>
} else {
|| {} // fallback
}
}
so that panic_on_zero::<0>
will never be instantiated.