Why isn't an array repeat operand subject to constant evaluation in a const context?

In the following code, why isn't the Wrap::new(2) expression evaluated at compile-time just like the Wrap::new(1) is?

struct Wrap(i32);

impl Wrap {
    const fn new(value: i32) -> Wrap {
        Wrap(value)
    }
}

fn testing() {
    let _arr = const {
        // `Wrap::new(1)` is evaluated at compile-time.
        let wrap = Wrap::new(1);

        // Changing the literal `1` to some other value makes this
        // fail at compile-time, proving that `Wrap::new(1)` was
        // evaluated at compile-time.
        assert!(wrap.0 == 1);

        // `Wrap::new(2)` is not evaluated at compile-time.
        // Must change the line to:
        // [const { Wrap::new(2) }; 10]
        [Wrap::new(2); 10]
    };
}

In this case, the array repeat operand must be const because its type (Wrap) doesn't implement Copy. The code fails to compile because for some reason the const context (an inline-const in this case but that's not relevant) does not force the repeat operand to be evaluated at compile-time, even though a const context does force all expressions that are not repeat operands to be evaluated at compile-time.

An unfortunate consequence of this is that you have to write:

let arr = [const { [const { AtomicU8::new(0) }; 10] }; 20];

..instead of:

let arr = const { [[AtomicU8::new(0); 10]; 20] };
1 Like

I guess… consistency / simplicity of the rules. At least in a slightly more complex case such as

struct Wrap(i32);

impl Wrap {
    const fn new(value: i32) -> Wrap {
        if value == 3 {
            panic!()
        };
        Wrap(value)
    }
}

fn testing() {
    let _arr = const {
        // `Wrap::new(1)` is evaluated at compile-time.
        let wrap = Wrap::new(1);

        // `Wrap::new(2)` is not evaluated at compile-time.
        // Must change the line to:
        // [const { Wrap::new(2) }; 10]
        if wrap.0 == 1 {
            [Wrap::new(2); 10]
        } else {
            [Wrap::new(3); 10]
        }
    };
}

here the change to

[const {Wrap::new(3)}; 10]

can introduce a compile time error (panic), whereas without it (but with a Copy impl), the panicking expression might never be evaluated.

I suppose, one could try to complicate the rules, so e.g. expressions in positions that are guaranteed to be evaluated don’t need the explicit nested const { … }, but at least that does complicate the rules. I’m not even sure whether I’d find that’s a bad thing, arguably, the usefulness can outweigh the complexity [really, there’s not possibly any surprising behavior here, right?], but at least no-one has implemented any mechanisms for something like this yet.


Regarding ways to improve

let arr = [const { [const { AtomicU8::new(0) }; 10] }; 20];

I think, another thing to consider, which might also be reasonable to think from: coming from the other side whether perhaps we can improve the language to accept

let arr = [[const { AtomicU8::new(0) }; 10]; 20];

instead.

1 Like

At least the documentation should be changed because the Constant Evaluation chapter in the Rust Reference claims that "Certain forms of expressions, called constant expressions, can be evaluated at compile time. In const contexts, these are the only allowed expressions, and are always evaluated at compile time", and the last part of that sentence is not true because in const contexts, a constant expression that is an array literal's repeat operand is not always evaluated at compile time.

3 Likes

There’s probably improvement that can be made, but an array’s element is not a “const context” by that definition, because constant expressions aren’t the only allowed expressions. A Copy expression is also valid. (That is, whether or not something is a “const context” is not type-sensitive, nor does it depend on whether it is itself nested in a “const context”.)

I assume that by an "array's element" you mean an array literal's repeat operand (the expression before the ; token). It's true that a repeat operand's place is not a const context. But everything inside a const block is a const context, and because the array literal in the code in my original post is inside a const block, the array literal's repeat operand is also in a const context.

By the way, it seems that the term "array literal" is not used in the Rust Reference, so I should have used the correct term "array expression".

Fair enough, const blocks clearly do act on subexpressions elsewhere.

I think it'd make sense for outer const {} to work on array initializers.

I suspect the reason why it doesn't work isn't a grand deliberate design, but rather a consequence of arrays in Rust being const-ish long before the language had real const eval support.