When should we be using explicit `const {}` blocks instead of letting compiler auto kick in?

Continuing the discussion from Why compiler error[E0492]: statics cannot refer to interior mutable data:

Reading about the new inline const block feature, I learned that a new type of block expression has been introduced to guarantee compile-time evaluation.

But it would be a pain to wrap expression in a const {} block every time I think it should be const evaluated, the same way I wouldn't want to spam #[inline] attributes everywhere. (In my mental model, fns in the same crate usually get inlined automatically without the need of an #[inline] attribute)

Is there some doc/article on const {} blocks roughly equivalent to this one?

Const blocks are very new, so there is no such article yet. However, they are very similar to constexpr from C++, and I believe that advice for when to use that in C++ would also apply to Rust const blocks. I recommend that you try to look for articles related to C++ constexpr.

1 Like

From what I learned, constexpr in C++ is more like const / const fn declarations in Rust instead of const{} which may wrap an expression that might never be bound to a named item.

I understand why const should be manually added to fn definitions, because fn signatures should be explicit about this without looking into the implementation.

And replacing let with const to emphasize different semantics adds only two letters, which is not a big deal.

But adding a layer of const {} wrapping everywhere is a big difference.

And I don't see how C++ constexpr is similar to this.


An example would be, should I rewrite this:

const fn my_const_fn(param: isize) -> isize {
    param
}

fn main() {
    let (mut a, mut b) = (1, 2);
    println!(
        "my_const_fn({a}) + my_const_fn({b}) = {}",
        my_const_fn(a) + my_const_fn(b)
    )
}

into this (the code below has been changed)?

const fn my_const_fn(param: isize) -> isize {
    param
}

fn main() {
    const A: isize = 1;
    const B: isize = 2;
    println!(
        "my_const_fn({A}) + my_const_fn({B}) = {}",
        const { my_const_fn(A) + my_const_fn(B) }
    );
    let (mut a, mut b) = (A, B);
}

To a first order of approximation, don't use const blocks as a "super inline" request. Their primary purpose is to allow you to get &'static _ without relying on static promotion or needing to manually hoist to a separate const item. If you wouldn't ever consider hoisting something into a const item, don't consider sticking it into a const block.

There will be edge cases where some pure computation can be const evaluated but LLVM will fail to constant fold it. But these will be quite rare and as usual, profile before optimizing.

Using const blocks also opens up the complexity pit that is post-monomorphization errors. Sometimes they might be preferable to runtime panics, but usually they're considered poor API design to expose and Rust doesn't expose any tools for deliberately conditional monomorphization yet that would help manage them.

You cannot, because the const still cannot use any context/input which is not itself const. Inline const blocks don't make anything new possible. They only make generics-capturing const practical to write. (Which is exactly why they make post monomorphization errors practical to write.)

3 Likes

Yeah I found out that, and I changed the code.

Should I be doing this kind of rewriting / does it make a difference?

Ah, right.

I'll answer with another question: why weren't you already doing this rewriting with named const items? If you wouldn't do it with a named const item, don't do it with a const block. You really can think of const { $expr } as effectively being sugar for { const _0: _ = $expr; _0 }.

This is basically a feature where if you actually want to use it, you already knew you wanted it. But outside of these applications (which primarily look like where you'd want static promotion, i.e. want &'static) you don't need it. Just allow the optimizer to make those decisions on your behalf until you have evidence suggesting it's worth your time to try to be smarter than the optimizer (by utilizing context the optimizer is too myopic to discover).

6 Likes

Got it.