Reputation: 418
In Rust, const
functions are quite limited in what code can be put inside them, e.g. for
loops aren't allowed, nor are any non-const
function calls. I understand that there are issues with heap allocations in const
functions, but why is it that for example the below code isn't valid:
fn add(a: u8, b: u8) -> u8 {
a + b
}
const A: u8 = add(1, 2);
This would be valid if add
was declared as a const
function.
const
functions necessary at all?for
loops allowed inside them (even though while loop
s are)?Upvotes: 6
Views: 6193
Reputation: 13538
Why are const functions necessary at all?
Constant functions are declared const
to tell the compiler that this function is allowed to be called from a const
context. Const-eval is done in a tool called miri
. Miri is not capable of evaluating all expressions, and so const
functions are pretty limited. There is a tracking issue of extensions that have been accepted and there is active work going on to make them const-compatible.
Why can't the Rust compiler detect that this is valid?
While technically possible, the Rust team decided against it. The primary reason being semantic versioning issues. Changing the body of a function, without changing the signature, could make that function no longer const-compatible and break uses in a const-context. const fn
is a contract, and breaking that contract is an explicit breaking change.
Why aren't even for loops allowed inside them (even though while loops are)?
As to why for
loops aren't allowed, they technically are. The only problem is that the Iterator::next
method is not const
, meaning that not some iterators may perform operations that are not const-evaluatable. Until a language construct that allows trait methods to be marked as const
, or the ability to put const
bounds, you will have to stick to regular loop
s.
const fn looop(n: usize) {
let mut i = 0;
loop {
if i == n {
return;
}
// ...
i += 1;
}
}
Upvotes: 14
Reputation: 42492
Why can't the Rust compiler detect that this is valid?
It can. The reason it won't is that removing const
is a backwards-incompatible change, and so must be opt-in: as the developer, by publishing a const
function you're making a very specific promise to user. Having the const-ness of a function automatically change without warnings based on its implementation details would be terrible.
It's very similar to the reason why Copy
isn't automatically implemented as soon as it could be: removing Copy
breaks code, so Copy
needs to be a guarantee which is opted into, while it's technically trivial change, it's absolutely not an innocuous change.
Why are const functions necessary at all?
Because the alternative (something like Zig's comptime or C++'s templates) doesn't really jive with Rust's safety philosophy, as well as its Elm-inspired desire to provide somewhat useful compiler errors.
Why aren't even for loops allowed inside them (even though while loops are)?
Have you considered looking? There's literally an RFC which tells you that: https://rust-lang.github.io/rfcs/2344-const-looping.html
for
loops are technically allowed, too, but can't be used in practice because each iteration callsiterator.next()
, which is not aconst fn
and thus can't be called within constants. Future RFCs (like https://github.com/rust-lang/rfcs/pull/2237) might lift that restriction.
Upvotes: 3
Reputation: 70347
We annotate const
functions for the same reason we annotate pub
functions: to make our intentions clear to the user. If you're writing a const
function, you probably know it and are doing so intentionally (since, as you've stated, const
functions are very limited in what they can do). By annotating it with a keyword, you
const
function,const
in it, andconst
.As for the for loops, they're just not the intention. If you want to do compile-time code generation, we have a whole subsystem for that: it's called macros and it's amazing. const fn
is for short functions that can be used in runtime context but that allow the added optimization of running at compile-time. As such, something like a loop (which may or may not terminate) adds a lot of complexity to that, and it can cause the compiler to fail to terminate. There are lots of infinite iterators, and determining whether the iteratee is infinite or not is equivalent to the halting problem. So it's easier to keep it simple and disallow this.
Upvotes: 4