`Vec::new()` is not promoted

Why does the following not work (Rust Playground)?

fn bar() -> &'static Vec<i32> {
    const VEC: Vec<i32> = Vec::new();
    &VEC
}
error[E0515]: cannot return reference to temporary value
 --> src/lib.rs:4:5
  |
4 |     &VEC
  |     ^---
  |     ||
  |     |temporary value created here
  |     returns a reference to data owned by the current function

I expected it would promote to static.

2 Likes

It doesn't work because a const value is folded in at compile time. So it's the same as writing

1 Like

It’s because promoting a Vec would change semantics. Implicit promotion only ever happens if the promotion does not change behavior, compared to if the code wasn’t promoted. Also note that there is no way of doing “only promote this if not doing so would result in lifetime errors” (because borrow checking is only ever supposed to check and possible reject code, never to influence semantics), an approach that would otherwise fix the problem of changing semantics, because code that used to not compile at all can of course not change its semantics.

The reason why promotion would change semantics is that Vec has a destructor.

If you write code like

struct Foo;
impl Drop for Foo {
    fn drop(&mut self) {
        println!("dropped");
    }
}

fn main() {
    {
        const FOO: Foo = Foo;
        &FOO
    };
    println!("Hello World");
}

then you expect the output

dropped
Hello World

and promoting &Foo to a &'static Foo would change the output, because the promoted value would never be dropped.


Of course, there could be an argument to be had that this particular code would actually not change semantics all that much as an empty Vec doesn’t allocate anything so destructing it does not do anything; or there could be heuristics not relying on the borrow checker (perhaps comparable to temporary lifetime extension for things like let x = &foo();) that could make the code at hand promote anyways (because it would for sure never compile otherwise), but at a certain point, you might also want promotion rules to stay reasonably straightforward and predictable :slight_smile: … and even if improving them seems reasonable after all, someone would need to come up with a way to actually do it.


You can read more about the rules at play here. Also see this RFC; quoting the relevant linked section:

Inside a function body's block:

  • If a shared reference to a constexpr rvalue is taken. (&<constexpr>),
  • And the constexpr does not contain a UnsafeCell { ... } constructor,
  • And the constexpr only consists of operations that will definitely succeed to evaluate at compile-time,
  • And the resulting value does not need dropping,
  • Then instead of translating the value into a stack slot, translate it into a static memory location and give the resulting reference a 'static lifetime.

Operations that definitely succeed at the time of writing the RFC include:

  • literals of any kind
  • constructors (struct/enum/union/tuple)
  • struct/tuple field accesses
  • arithmetic and logical operators that do not involve division: +/-/*, all bitwise and shift operators, all unary operators

The expression &VEC in this example would meet all conditions except the one that the value does not need dropping (at least to the current type-based analysis of which kinds of values “require dropping”).


By the way, in case that isn’t clear, you can of course explicitly make your code work, either by doing something like this, promoting the constant to a static yourself

fn bar() -> &'static Vec<i32> {
    static VEC: Vec<i32> = Vec::new();
    &VEC
}

or via something as follows that also works, even though the precise rules/reasoning at play are even too complicated that I would remember them in detail off the top of my head:

fn bar() -> &'static Vec<i32> {
    const VEC: &Vec<i32> = &Vec::new();
    VEC
}
9 Likes

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.