Argument requires that ... is borrowed for `'static`

I have a problem with lifetimes, which seems pretty simple yet I couldn't get my head around it.

Here is the code (playground):

struct Encoder {}

impl Encoder {
    pub fn pass<'a>(&'a mut self) -> RenderPass<'a> {
        RenderPass { parent: self }
    }
}

struct RenderPass<'a> {
    parent: &'a Encoder,
}

impl<'a> RenderPass<'a> {
    pub fn set(&mut self, _x: &'a i32) {}
}

fn main() {
    let value = 1;
    
    render(|pass| {
        pass.set(&value);
    });
}

fn render(mut f: impl FnMut(&mut RenderPass))
{
    let mut encoder = Encoder {};
    let mut pass = encoder.pass();
    
    f(&mut pass);
    
    drop(pass);
}

The error is:

error[E0597]: `value` does not live long enough
  --> src/lib.rs:21:19
   |
20 |     render(|pass| {
   |            ------ value captured here
21 |         pass.set(&value);
   |         ----------^^^^^-
   |         |         |
   |         |         borrowed value does not live long enough
   |         argument requires that `value` is borrowed for `'static`
22 |     });
23 | }
   | - `value` dropped here while still borrowed

The question is why value should be borrowed for 'static? Is there any way to solve this case? Thanks in advance!

3 Likes
  1. Start with #![deny(elided_lifetimes_in_paths)] at the root of your src/{lib,main}.rs file.

  2. From there, it should lint about the missing lifetime parameter in, for instance, render's signature, expecting something like:

    fn render (mut f: impl FnMut(&mut RenderPass<'_>))
    
  3. Now to apply the rules of lifetime elision:

    1. Identify the lifetime parameters / placeholders / "holes":

      //                           v               vv
      fn render (mut f: impl FnMut(&mut RenderPass<'_>))
      

      i.e.,

      //                            vv                vv
      fn render (mut f: impl FnMut(&'_ mut RenderPass<'_>))
      
    2. Find the closest "function-signature boundary" surrounding those parameters. These can be:

      • The fn render function itself;

      • fn(…) -> … pointers

      • impl or dyn Fn{,Mut,Once}(…) -> … traits

    3. And, "at that level", for each placeholder in function-input position, introduce new and distinct generic lifetime parameters / names.

      In our case, the "level" is FnMut:

      // pseudo-code!
      fn render (
          mut f: impl FnMut<'_0, '_1> (&'_0 mut RenderPass<'_1>),
      )
      

      In the case of traits or fn(…) -> … pointers, these generics are actually written using for<…> syntax:

      // Real code!
      fn render (
          mut f: impl for<'_0, '_1> FnMut(&'_0 mut RenderPass<'_1>),
      )
      

These are called Higher-Rank Trait Bounds (see also Lifetime Quantification and Higher-Ranked Trait Bounds — Infinite Negative Utility), and are used to express an infinite sum/family of bounds; the bound applies for every possible choice of each lifetime parameter:

for<'_0, '_1> Stuff<'_0, '_1>

// is the same as

Stuff<'static, 'static> +
Stuff<'static, 'a> +
Stuff<'static, 'b> +
Stuff<'static, '…> +

Stuff<'a, 'static> +
Stuff<'a, 'b> +
Stuff<'a, '…> +

Stuff<'b, 'static> +
Stuff<'b, 'b> +
Stuff<'b, '…> +

Stuff<'…, 'static> +
Stuff<'…, 'b> +
Stuff<'…, '…> +

In our case, for<'_0, '_1> FnMut(&'_0 mut RenderPass<'_1>) thus includes a bound such as:

FnMut(&'whatever mut RenderPass<'static>)

This is how 'static managed to creep into your code :grinning_face_with_smiling_eyes:.

To fix this, back to the pseudo code, remember that you had:

fn render (
     mut f: impl FnMut<'any, …> (&mut RenderPass<'any>),
)

where 'any had that universal / higher-level / higher-quantification that meant that it included the 'any = 'static requirement: the caller has no say in this!

What the caller can choose are generic parameters at the level of the function itself (here, render):

  • higher-order generic lifetimes: universal, not chosen by the caller! :warning:

    • Often these are needed when the lifetime is callee-chosen
  • "normal" generic lifetimes: chosen by the caller :white_check_mark:

So we want to do:

fn render<'caller_chosen> (
    mut f: impl FnMut(&mut RenderPass<'caller_chosen>),
)

and this does remove the 'static requirement.

We thus reach the following

which does compile at the call-site, but now the callee-site fails with:

error[E0597]: `encoder` does not live long enough
  --> src/main.rs:31:20
   |
28 | fn render<'caller_chosen>(mut f: impl FnMut(&mut RenderPass<'caller_chosen>))
   |           -------------- lifetime `'caller_chosen` defined here
...
31 |     let mut pass = encoder.pass();
   |                    ^^^^^^^^^^^^^^
   |                    |
   |                    borrowed value does not live long enough
   |                    assignment requires that `encoder` is borrowed for `'caller_chosen`
...
36 | }
   | - `encoder` dropped here while still borrowed

So here it turns out that the higher-order lifetime was warranted / we did need to let the callee pick its own "arbitrary"(ly small) lifetime.

  • We will need to go back to for<'encoder> lifetime parameters.

But when we did do that, Rust complained about an arbitrarily big lifetime ('static)!

Well, now we know that we want to allow for arbitrarily low / small lifetimes! And it just happens that the naïve / default for<'any> syntax…

  • requires support for arbitrarily small lifetimes (good for the callee) :white_check_mark:

  • while also requiring support for arbitrarily big lifetimes (bad for the caller!) :x:

So we just need to opt out of the latter.

In an ideal world, this would be written as:

// pseudo-code!
fn render<'upper_bound, F> (mut f: F)
where
                    // 'upper_bound ⊇ 'encoder
    for<'encoder where 'upper_bound : 'encoder>
        F : FnMut(&mut RenderPass<'encoder>)
    ,

Sadly we cannot yet introduce explicit bounds for higher-order lifetimes; we'll thus need to ruse and abuse the fact that we can use implicit bounds!

fn render<'upper_bound, F>(mut f: F)
where
    for<'enc>
        F : FnMut(&mut RenderPass<'enc>, [&'enc &'upper_bound (); 0])
        //                               ^^^^^^^^^^^^^^^^^^^^^^^^^^^
        //                         HACK: this empty array parameter,
        //                         akin to `()` or `PhantomData`,
        //                         carries no runtime data.
        //                         But in the type-system, at compile-time,
        //                         the ref represents a type which is
        //                         only valid when the inner lifetime
        //                         is bigger than the outer one;
        //                         i.e., mentioning this type yields
        //                         a `'upper_bound : 'enc` bound
    ,
{
    let mut encoder = Encoder {};
    let mut pass = encoder.pass();
    
    f(&mut pass, []);
    
    drop(pass);
}
  • Playground

  • This quirk may be more readable by defining a

    type PhantomBound<'big, 'small> = PhantomData<&'small &'big ()>;
    

    and then write the signature as:

    for<'enc>
        F : FnMut(&mut RenderPas<'enc>, PhantomBound<'upper_bound, 'enc>)
    ,
    

Improving the ergonomics

If you don't want that extra quasi-vestigial parameter to be that visible in the closure, if you control the definition of RenderPass, you can embed the 'upper_bound inside it, which yields the following API (which does compile!):

fn main ()
{
    let value = 1;
    
    render(|pass| {
        pass.set(&value);
    });
}

fn render<'upper_bound, F>(mut f: F)
where
    for<'enc>
        F : FnMut(&mut RenderPass<'enc, 'upper_bound>)
    ,
// or just:
    F : FnMut(&mut RenderPass<'_, 'upper_bound>),
{
    let mut encoder = Encoder {};
    let mut pass = encoder.pass();
    
    f(&mut pass);
    
    drop(pass);
}

The implementation of it is then just:

#![deny(elided_lifetimes_in_paths)]
  
  struct Encoder {}
  
  impl Encoder {
-     pub fn pass<'a>(&'a mut self) -> RenderPass<'a> {
+     pub fn pass<'a>(&'a mut self) -> RenderPass<'a, 'static> {
          RenderPass { parent: self, _upper_bound: <_>::default() }
      }
  }
  
- struct RenderPass<'a> {
+ struct RenderPass<'a, 'upper_bound : 'a> {
      parent: &'a Encoder,
+     _upper_bound: ::core::marker::PhantomData<&'upper_bound ()>,
  }
  
- impl<'a> RenderPass<'a> {
+ impl<'a> RenderPass<'a, '_> {
      pub fn set(&mut self, _x: &'a i32) {}
  }
  • EDIT: this is the approach since taken by the standard library itself in their scoped threads API: notice their 'env upper bound on the higher-order 'scope.
33 Likes

Btw, this whole previous explanation assumed that you needed the following signature to remain untouched:

impl<'encoder> RenderPass<'encoder> {
    pub fn set(&mut self, _x: &'encoder i32) {}
}

i.e., that you needed that _x to be a borrow lasting as long as the RenderPass itself / as long as the borrow of the encoder.

If, on the contrary, you were able to clone or copy the input _x so as not to keep borrowing it, then your whole snippet would compile by doing:

impl<'a> RenderPass<'a> {
-   pub fn set(&mut self, _x: &'a i32) {}
+   pub fn set(&mut self, _x: &'_ i32) {}
}

which is way simpler than abusing implicit bounds in higher-order signatures :upside_down_face:

7 Likes

Hi @Yandros,

I looked at this example and wondered, "what if we wanted to keep set how it is but were okay with the 'static bound on render?" It's not bad if we add a trait:

// I went with `String`/`str` to make sure I didn't exploit `Copy`
impl<'a> RenderPass<'a> {
    pub fn set(&mut self, _x: &'a str) {}
}

// We have a trait that can do things with `RenderPass<'a>`
// if you can borrow `Self` for `'a`
trait SetupPass<'a> {
    fn setup(&'a self, rp: &mut RenderPass<'a>);
}

// Straight-forward blanket implementation for any T: 'static
impl <'a> SetupPass<'a> for String {
    fn setup(&'a self, rp: &mut RenderPass<'a>) {
        rp.set(self);
    }
}

// Rely on this instead of `FnMut`
fn render2(f: impl for<'all> SetupPass<'all>) {
    let mut encoder = Encoder {};
    let mut pass = encoder.pass();
    f.setup(&mut pass);
}

fn main() {
    // Works
    render2(String::new());
}

But I haven't found a way to get it to work in closure form:

fn main() {
    // Conceptually the closure is just a wrapper around `String`
    let value = String::new();
    render(move |pass| {
        pass.set(&value);
    });
}

fn render(mut f: impl FnMut(&mut RenderPass<'_>)) { ... }

I've tried to guide inference in a few ways with the standard function roundtrips, but the compiler really seems to want to find a concrete lifetime for &value here, or insists on tying it to the &mut instead of the RenderPass perhaps.

Any insights? Inference shortcoming or fundamental limitation I'm overlooking?

Hopefully this isn't hijacking the thread too much...

Thanks for this well-detailed answer!

As far as I understand the explanation, we didn't let the caller to choose the lifetime, hence, the 'static was chosen because why not, noone said nothing about lifetimes, so the biggest one was used. However, passing an arbitrary lifetime 'caller_chosen didn't work but this time at the callee side since such a lifetime is not strictly less than the lifetime of the function itself (thus, the error that encoder is dropped while being borrowed).

So, the trick is to let the caller to choose a different lifetime, not the one that is used to borrow the render pass from the encoder. Wow, this looks brilliant! It also explains why I didn't come up with a solution after a few hours of digging.

Currently, I decided to wrap the RenderPass (since it's a type from the wgpu lib) into another structure with Deref and DerefMut implemented for it. Works like a charm!

2 Likes

This is a great way to get for<'defined: 'capped> .... I've wanted that or its complement many times. (I almost missed it since your original reply stopped early.) Doesn't seem to help with the captured case though, that's something else.

I may get it though. If we think of returning a borrow instead of the &mut argument capturing the borrow, a higher-ranked trait works out as there as an implementation per borrowed lifetime. But the Fn traits are only higher-ranked over their arguments, and not for the lifetime on &self in Fn::call. Therefore you would need GATs to allow "references to captured variables [to] escape the closure."

1 Like

:thinking: Interesting question!

I think there is a tiny difference between impl for<'all> SetupPass<'all>, and impl FnMut(&mut RenderPass<'_>). Indeed, let's reduce both to FnOnce shapes:

T : FnMut(&mut RenderPass<'_>)
// i.e.,
for<'a, 'enc>
    T : FnMut(&'a mut RenderPass<'enc>)
,
// which ought to be equivalent to:
// (in practice the trait solver gets confused with higher-order `&mut Self` bounds)
for<'call, 'a, 'enc>
    &'call mut T : FnOnce(&'a mut RenderPass<'enc>)
,
// cleanup: elide `'a`
for<'call, 'enc>
    &'call mut T : FnOnce(&mut RenderPass<'enc>)
,

vs.

T : for<'all> SetupPass<'all>,
// i.e.,
for<'enc>
    T : SetupPass<'enc>
,
// which is analogous to:
for<'a, 'enc>
    &'enc mut T : FnOnce(&'a mut RenderPass<'enc>)
,
// cleanup: elide `'a`
for<'enc>
    &'enc mut T : FnOnce(&mut RenderPass<'enc>)
,

Notice how the lifetime parameter of the &mut self is free / an extra-level of higher-orderness for the FnMut call, whereas for the SetupPass<'_> case, it is coupled to the inner lifetime parameter used for the RenderPass. So if 'enc gets to be 'static, in the latter case, this would only happen if the &mut borrow of self were that long, hence dodging the issue, whereas for the FnMut… ~ for<'any> &'any mut Self : FnOnce… case, the &mut self borrow could remain independently small, hence making the implementation impossible.

So, the closest you could go to that helper trait approach with Fn… bounds only would be to write that FnOnce equivalent definition to SetupPass<'_>:

fn render<F> (mut f: F)
where
    for<'enc>
        &'enc mut F : FnOnce(&'_ mut RenderPass<'enc>)
    ,
{
    let mut encoder = Encoder {};
    let mut pass = encoder.pass();

    // `feature(fn_traits)`, since `f(&mut pass)` confuses Rust
    (&mut f).call_once((&mut pass, ));

    drop(pass);
}

which painfully solves the callee issues, but then makes the function uncallable with normal closures, since the Rust-generated anonymous closures don't get to feature such lifetime-tangled higher-order signatures.

In practice, in cases such as this one, the solution is to use a helper trait such as your SetupPass (and, for instance, implement it for
(State, for<'enc> fn(&'enc mut State, &mut RenderPass<'enc>)) tuples),
or to directly require such tuples:

fn main() {
    let value = String::new();
    
    render(value, |at_value, pass| {
        pass.set(at_value);
    });
}

fn render<State> (
    mut state: State,
    f: for<'enc> fn(&'enc mut State, &mut RenderPass<'enc>),
)
{
    let mut encoder = Encoder {};
    let mut pass = encoder.pass();
    
    f(&mut state, &mut pass);
    
    drop(pass);
}
  • Playground

  • (Yes, fn(…) is not necessary there, it was just to show the idea of splitting a closure into its bare constituents, but the truth is the fn(…) part does not need to be expressed with runtime info, it can indeed be encoded in an extra closure type which is, in practice, expected to be capture-less:
    (State, impl 'static + Send + Copy + for<'enc> FnOnce(&'enc mut State, …) )

5 Likes

It seems like things always get confusing fast when higher-order lifetimes show up…

4 Likes

Error, Confusing is not general enough; expected Confusing<'_>, found Confusing<'_>

17 Likes

Why must you hurt me so :sob:

5 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.