Search code examples
rustlifetimeborrow-checker

Why does taking a static reference to a const return a reference to a temporary variable?


In Rust I have the following code:

pub trait Test: Sized {
    const CONST: Self;
    fn static_ref() -> &'static Self {
        &Self::CONST
    }
}

My expectation is that since const is 'static, then I should be able to take a reference to it that is also 'static. However, the compiler gives the following error:

error[E0515]: cannot return reference to temporary value
   --> file.rs:9:9
    |
  9 |         &Self::CONST
    |         ^-----------
    |         ||
    |         |temporary value created here
    |         returns a reference to data owned by the current function

How is a temporary variable being introduced here?

Additionally, it seems that there are some cases where taking a reference to a constant does work. Here is a short concrete example with a slightly different implementation of Test

pub trait Test: Sized {
    fn static_ref() -> &'static Self;
}

struct X;

impl Test for X {
    fn static_ref() -> &'static Self {
        &X
    }
}

Solution

  • const and static are different things, but are often confused with each other.

    static bindings hold data that lives in a permanent memory location for the duration of the program. That's why you can return references to them from functions; they live forever so you can give them the 'static lifetime.

    const bindings have values that don't change and can be reasoned about at compile-time just as well as at run-time. The compiler will inline the expression used to define the const everywhere that it is used. It's likely that two usages of the same constant will store it in different memory addresses and may even reapply the computation used to construct it.

    Consider a slightly more complicated example:

    struct MyStruct;
    
    impl MyStruct {
        const fn new() -> Self {
            MyStruct
        }
    }
    
    impl Test for MyStruct {
        const CONST: Self = MyStruct::new();
    }
    

    This won't work because, since constants are inlined, the implementation of static_ref will now look like this:

    fn static_ref() -> &'static Self {
        &MyStruct::new()
    }
    

    It's creating a value inside the function and trying to return it. This value is not static, so the 'static lifetime is invalid.


    However, with a little re-jigging, you can make something work:

    pub trait Test: Sized + 'static {
        // This is now a reference instead of a value:
        const CONST: &'static Self;
    
        fn static_ref() -> &'static Self {
            Self::CONST
        }
    }
    
    struct MyStruct;
    
    impl MyStruct {
        const fn new() -> Self {
            MyStruct
        }
    }
    
    impl Test for MyStruct {
        const CONST: &'static Self = &MyStruct::new();
    }
    

    This works because CONST is already a 'static reference, so the function can just return it. All possible implementations would have to be able to obtain a 'static reference to Self to implement the trait, so there is no longer an issue with referencing a temporary value.