Compiler error when referring to a const value (initialised by a const fn)

With the following code Snipet, I get an error when referencing CONST_VALUE_ERR_2.
Is this behaviour expected / documented, or is it a bug?

struct Foo<T>
{
    value : T
}

impl<T> Foo<T> {
    const fn new(value: T) -> Self {
        Self {  value }
    }
}

fn get_foo<T>(_obj : &'static Foo<T>){
    //use as &static Reference Foo
}


const CONST_VALUE_OK_1 : Foo<Option<Box<u32>>> = Foo{ value : None};
const CONST_VALUE_ERR_2 : Foo<Option<Box<u32>>> = Foo::new(None);
const CONST_VALUE_OK_3 : Foo<Option<u32>> = Foo{ value : None};
const CONST_VALUE_OK_4 : Foo<Option<u32>> = Foo::new(None);

fn main(){
    get_foo(&CONST_VALUE_OK_1);
    get_foo(&CONST_VALUE_ERR_2); // compiler error: temporary value dropped while borrowed 
                                // creates a temporary value which is freed while still in use
                                // temporary value is freed at the end of this statement
                                // argument requires that borrow lasts for `'static`
    get_foo(&CONST_VALUE_OK_3);
    get_foo(&CONST_VALUE_OK_4);
}

Compiler Version: rustc 1.77.2 (25ef9e3d8 2024-04-09)

It looks like the compiler is copying the expression assigned to the const variable when a box is in the generic?

    get_foo(&Foo::<Option<Box<u32>>>{ value : None}); //ok
    get_foo(&Foo::<Option<Box<u32>>>::new(None));     //same error
    get_foo(&Foo::<Option<u32>>::new(None));              //is also an error, but 
    get_foo(&CONST_VALUE_OK_4);                             //was ok?

The difference is that the working ones get const promoted.

That is, the promoted expression can be evaluated at compile-time and the resulting value does not contain interior mutability or destructors (these properties are determined based on the value where possible, e.g. &None always has the type &'static Option<_>, as it contains nothing disallowed).

The details are unclear, but it's apparent that rustc has decided that a function call that produces a type with a destructor isn't const promoted.

Interestingly, this works:

const CONST_VALUE_OK_1: &Foo<Option<Box<u32>>> = &Foo { value: None };
const CONST_VALUE_ERR_2: &Foo<Option<Box<u32>>> = &Foo::new(None);
const CONST_VALUE_OK_3: &Foo<Option<u32>> = &Foo { value: None };
const CONST_VALUE_OK_4: &Foo<Option<u32>> = &Foo::new(None);

fn main() {
    get_foo(CONST_VALUE_OK_1);
    get_foo(CONST_VALUE_ERR_2);
    get_foo(CONST_VALUE_OK_3);
    get_foo(CONST_VALUE_OK_4);
}

Perhaps this is using temporary lifetime extension instead of const promotion? But this isn't really better than using statics, which work either way.

static CONST_VALUE_OK_1: Foo<Option<Box<u32>>> = Foo { value: None };
static CONST_VALUE_ERR_2: Foo<Option<Box<u32>>> = Foo::new(None);
static CONST_VALUE_OK_3: Foo<Option<u32>> = Foo { value: None };
static CONST_VALUE_OK_4: Foo<Option<u32>> = Foo::new(None);

fn main() {
    get_foo(&CONST_VALUE_OK_1);
    get_foo(&CONST_VALUE_ERR_2);
    get_foo(&CONST_VALUE_OK_3);
    get_foo(&CONST_VALUE_OK_4);
}

It's not the box...

// Fails (you noted this too)
get_foo(&Foo::<Option<i32>>::new(None));

...(see @drewtato's reply), but yes, uses of const are like putting the entire expression at the use site.

I think this one is just from new being called in a non-const context, so it never gets const promoted.


More thoughts on the original thing: a reason why Rust wouldn't allow get_foo(&CONST_VALUE_ERR_2); is that Foo::new could produce an actual droppable value. Not Box, but there are droppable types that don't heap allocate. And this upholds the rule of Rust that as long as the function signature stays the same, changing the contents of the function can't cause uses of the function to stop compiling.

1 Like

This is the best article I know on the topic(s) (though I haven't internalized it all myself yet). The thing allowing some versions to work is indeed const promotion, and this works (on nightly) too.

#![feature(inline_const)]
fn main() {
    get_foo(const { &CONST_VALUE_ERR_2 });
}

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.