Cannot get a static reference when indexing a static array

Link to the playground

The code:

struct Foo;
impl Foo {
    const fn bar(&self) -> &'static [&'static [&'static str]] {
        &[ &[ "paf" ] ]
    }
    
    fn baz(&self) -> &'static [&'static [&'static str]] {
        &[ &[ "paf" ] ]
    }
}

const BAR: &'static [&'static [&'static str]] = Foo.bar();
//const BAZ: &'static [&'static [&'static str]] = Foo.baz();

struct Baz;
impl Baz {
    fn bar(&self) -> &'static [&'static [&'static str]] {
        //&[ Foo.bar()[0] ]
        &[ BAR[0] ]
    }
}

In this snippet, you see that I cannot return a static reference to an array (in Baz), because cannot return reference to temporary value.

Note that returning BAR or Foo.bar() or even Foo.baz() works just fine - the problem is dereferencing the array creates this "temporary value".

However, everything here is a static reference, so I expected this to just work. After all, even if the dereferencing itself is temporary, the returned value has the appropriate lifetime. Is there a soundness issue I am not seeing or could this be considered a bug/limitation in the borrow checker?

impl Baz {
    fn bar(&self) -> &'static [&'static [&'static str]] {
        // &[ Foo.bar()[0] ]
        const B: &'static [&'static str] = BAR[0];
        &[B]
    }
}

https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=6e7fd6be72feb9321e19d983befa14d7

When static promotion falls short, you can explicitly get &'static by defining const items instead.

p.s. the reason that indexing expression is not promoted is 3027-infallible-promotion - The Rust RFC Book and promotability .

2 Likes

This is not the case. The expression

&[ BAR[0] ]

does

  • read (i.e. copy) the value of BAR[0] of type &'static [&'static str] into a temporary local variable on the stack, because BAR[0] is accessed by-value when passed as an element to the array expression
  • the array expression [ … ] around BAR[0] takes this value and moves/copies it into a newly created single-element array stored in a temporary local variable on the stack, of type [&'static [&'static str]; 1]
  • then the & … borrow expression around that takes a reference to the temporary variable, of type &'lifetime [&'static [&'static str]; 1]. This 'lifetime can be at most as long as the temporary variable lives
  • finally, implicit coercion turns the reference-to-array into a reference-to-slice of type &'lifetime [&'static [&'static str]]

Then we need to consider lifetime extension rules. Lifetime extension rules take temporary values and instead put them someplace else that lives a bit longer, in order to reduce some lifetime errors. Rust has two kinds of promotion rules, one for storing it into a local variable in the same block, and one for promoting it to a static constant. These rules have restrictions that make sure they cannot surprise you by changing your program’s behavior.

  • temporary lifetime extension” placing data into a local variable
    • this promotion actually could take place here[1], so if you were to write let x = &[ BAR[0] ]; useage_of(x); the array of type [&'static [&'static str]; 1] containing [ BAR[0] ] wouldn’t just live in a temporary but a variable that can live up to the end of bar. Still not 'static
  • rvalue static promotion” (unfortunately not very well-documented yet)
    • this is what powers your bar/baz function
    • it doesn’t allow a whole lot of operations that could potentially panic, as promoting to a static would then turn the panic from compile-time into run-time error, which is changing program behavior

To “help out” the static promotion, as @vague already demonstrated, you can use a const item. Usage of the const is a kind of expression that’s eligible for use in static promoted expressions, as the concern about “changing program behavior” no longer applies (since we already manually made it so that any failure would happen at compile-time). In generic contexts, you might even need the help of a trait with associated const items, as we don’t have generic consts (yet). We also don’t have const { … } blocks (yet), which would eventually become the most syntactically lightweight way to do this:

impl Baz {
    fn bar(&self) -> &'static [&'static [&'static str]] {
        &[const { BAR[0] }]
    }
}

(playground)


  1. I think technically it doesn’t as this is a directly returned expression ↩︎

3 Likes

I see, I guess I was const-evaluating everything as I wrote/read it, i.e. if Foo::bar worked, why not Bar::bar, without realizing that, to the compiler, BAR[0] is still a fallible operation.

The original use-case was for code generation, where forwarding the call would have been a bit nicer than generating const values, but at least I have a workaround now.

Thank you and @vague for the explanation and links!