Today, as far as I'm aware, Rust has two variants of const functions:
- const-capable functions that are only used as
constwhen called fromconstcontext (regularconst fn) - "comptime" const functions that can only be called during compile time: Add very basic "comptime" fn implementation by oli-obk · Pull Request #148820 · rust-lang/rust · GitHub
What I often want though is something I would call const-first function. It is a function very similar to const fn, but such that is always evaluated at compile time, assuming all arguments are known at compile time. It could have been the default, but I am aware of open quesstions in Tracking Issue for const_eval_select · Issue #124625 · rust-lang/rust · GitHub, and it seems unlikely because of that. And even if it was, there would still be a need for some kind of opt-out for size-constrained environments.
I have two related examples for where this is valuable, both involving heavy use of const generics.
Miri
I have implemented an algorithm that uses a few global constants and a few const generics. Once parametrized the code compiles to an efficient and mostly branchless code by LLVM.
Here is an example of such a function:
Every branch there is eliminated at compile time, every shit is known at compile time too. The issue is that the resolution of these values seems to happen too late.
This is likely why running tests that call this function under Miri take an unacceptably long time in CI. It does finish eventually, but take way too long to be usable.
My current understanding is that this is happening due to Miri needing to evaluate a lot of expressions, function calls and conditions in runtime despite almost everything here known at compile time. A lot of runtime compute could have been saved if as much as possible was optimized early.
rust-gpu
I have also implemented the same algorithm on the GPU too using rust-gpu, here is an equivalent function there:
While not exactly the same, I did hit an issue of a similar nature with it, where, initially, compiled shader required Int8 capability (ability to use 8-bit integers, which is not the default capability for all GPUs). The reason for that was again computation that was fully known at compile time technically, but not evaluated at compile time by Rustc due to lack of const-first functions.
I do not know enough about rust-gpu internals, but it is clear that whatever stage it takes the input from was not optimized basically at all (I have more experience with it pointing to this being the case). My workaround was to use const {} in some places and some ugly unrolling of expressions in others.
The annoying thing is that while I can use const {} once, after assigning it to a variable I can't use variable's output in another const {} block. I believe generic_const_args might have a bit nicer ergonomics in this regard than generic_const_exprs linked examples uses, but it is still a major hit to ergonomics forcing me to write unnatural code.
Ideal solution
Ideally, I'd like to be able to have a const function that always evaluates at compile time as long as all inputs are known at compile time. Moreover, if an expression is composed only from inputs that are known at compile time, compute it recursively too. I don't think this requires any sophisticated analysis and should be easily parallelizable.
For example, foo2 would be reduced to a constant by Rustc and involve zero compute for Miri or Int8 capability for rust-gpu in the following example:
const A: u8 = 5;
const B: u8 = 3;
let foo1 = u32::from(5) << B;
let foo2 = foo1 * 16;
...
I believe most of the const functions in the standard library and otherwise will be const-first. Only rare cases when the function derives some large amount of data from small amount of data (increasing binary size) or the computed function itself needs heavy optimizations (impact compile time too much otherwise), it would be preferred to not compute it at compile time.
Today this seems to be done on LLVM level (I might be totally wrong about this though), but I think it would be beneficial, at least for both examples above, to do that much, much earlier.