The effects of const fn

I saw this over in the QoTW, and had a thought I wanted to share for discussion.

My issue with this is not that it's wrong, but that it considers the role of the compiler too narrowly. Many of my favorite writings about rust treat the compiler not just as a transformer of code, but also as a teacher of the community and a communicator between devs about meaning and values.

What that implies in this case is that const fn does two additional things: it assures the programmer that they have written their code I such a way that the compiler knows how optimize it for compile time evaluation (compiler as educator), and it forms a contract between the author and their users that the author will not change the function in any way that makes it non-const in the future (compiler as communicator).

While it may be true that the only thing const fn does in terms of language translation is slow code to be called in const contexts, I think it's valuable to keep in mind the other roles of the compiler, and to acknowledge that const fn has other real effects.

3 Likes

The compiler does no such thing. const fn really only adds language capabilities (callable in const context) and API guarantees for downstream crates.

2 Likes

@cliff that is a fair point -- const is a API guarantee made by the library author that all operations performed inside this function can be evaluated at compile-time. That can be seen as an "effect" as well.

But there is no interaction with the optimizer so I am not sure what you mean by "optimize it for compile time evaluation".

1 Like

Even if there aren’t any guarantees, the fact that the function can be fully evaluated at compile time also gives some assurance that a sufficiently smart optimizer will be able to significantly reduce it in effectively const situations that aren’t formally const.

I end up explicitly engineering situations that will always produce the same answer after monomorphization, for example, but uses generic parameters in ways that the constexpr system can’t handle yet.

1 Like

That's the thing, it gives no such assurance at all. Compile-time evaluated code may still panic or run into infinite loop, so figuring out if the optimizer can do constant propagation on them is not really any simpler than for normal fn.

1 Like

Sure, proving that the optimizer will be able to constant-fold a const fn call is no simpler than for a normal fn, but it’s a reasonable heuristic at the moment.

That may change over time as const fns get more powerful and used in new ways by the community. But for now, I feel reasonably confident that one of the branches will be optimized away in bar::<MyStruct> here:

const fn foo(u32)->bool { /* ... */ }

impl Trait for MyStruct {
    #[inline(always)]
    fn get_foo_arg(&self) -> u32 { 42 }
}

fn bar<T:Trait>(x: T) {
    if foo(x.get_foo_arg()) {
        /* do stuff */
    } else {
        /* do other stuff */
    }
}
1 Like