Lifetime issues: argument requires that `'1` must outlive `'2`

fn main() {
    let p = RenderPipeline {
        n: 1
    };
    
    let r1 = RenderPass {
        n: &1
    };
    
    let r2 = RenderPass {
        n: &2
    };
    
    let c = move |mut rpass: RenderPass| {
        rpass = triangle(rpass, &p);
    };
    
    c(r1);
    c(r2);
}

fn triangle<'a>(mut pass: RenderPass<'a>, pipe: &'a RenderPipeline) -> RenderPass<'a>{
    pass.set_pipeline(pipe);
    pass
}

struct RenderPass<'a> {
    n: &'a i32
}

impl <'a> RenderPass<'a> {
    pub fn set_pipeline(&mut self, pipeline: &'a RenderPipeline) {
        
    }
}

struct RenderPipeline {
    n: i32
}

This code generates an error: lifetime may not live long enough

What is the problem?

Following change resolves the problem.

- let c = move |mut rpass: RenderPass| {
+ let c = |mut rpass| {
      rpass = triangle(rpass, &p);
};

There's something subtle going on. By writing out the type (with elided lifetime), the closure shall accept RenderPass<'a> for any lifetime 'a, which could live longer than p, resulting the error. If you omit that, then the compiler can infer the correct lifetime bounds.

Basically, this issue could be simplified to this snippet:

fn main() {
    let i = 1;
-   let x = |mut x: &i32| { x = &i; };
+   let x = |mut x| { x = &i; };
}

Another way to fix the error is do not reassigning the out pipeline to he origin variable. But declare a new variable instead.

let c = move |mut rpass: RenderPass| {
-       rpass = triangle(rpass, &p);
+       let rpass2 = triangle(rpass, &p);
};

This way, rpass2 and rpass can have different lifetime, so p is not required to outlive any rpass. (rpass's lifetime will be shortened to local scope when being passed to triangle.)

2 Likes

Omiting the type throws another error: borrowed data escapes outside of closure

Oh yes. my bad. You need to remove the move too. I forgot to add that. (I edited the original reply)

I see, thank you for the quick response. Though that works for the original example, it doesn't for my actual use case.

This code below resembles more what I'm trying to achieve.

fn main() {
    let p = RenderPipeline {
        n: 1
    };
    
    let c = move |mut rpass: RenderPass| {
        rpass = triangle(rpass, &p);
    };
    
    draw(c);
}

fn triangle<'a>(mut pass: RenderPass<'a>, pipe: &'a RenderPipeline) -> RenderPass<'a>{
    pass.set_pipeline(pipe);
    pass
}

struct RenderPass<'a> {
    n: &'a i32
}

impl <'a> RenderPass<'a> {
    pub fn set_pipeline(&mut self, pipeline: &'a RenderPipeline) {
        
    }
}

struct RenderPipeline {
    n: i32
}

fn draw<F>(f: F) where F: 'static + Fn(RenderPass) {
    let rpass = RenderPass {
        n: &1
    };
    
    f(rpass);
}

Rust Playground

    let c = move |mut rpass: RenderPass| {
        let mut rpass = triangle(rpass, &p);
    };

Change the closure body to this should fix your issue. (it's the second fix in my original reply).

1 Like

Ahh thanks, that works! Awesome!

I do feel like I want to try passing RenderPass as a mutable reference to the triangle function. Is there a more simple scenario to explain why this doesn't work?

Something like this:

Rust Playground

Because the type is covariant (in the example at least), you can introduce a new binding to shorten the lifetime the caller chose to pass in to a lifetime that is shorter than the closure body:

-    let c = move |mut rpass: RenderPass| {
+    let c = move |rpass: RenderPass<'_>| {
+        let mut rpass = rpass;
         triangle(&mut rpass, &p);
     };

Or...

-    let c = move |mut rpass: RenderPass| {
+    let c = move |mut rpass @ _: RenderPass<'_>| {

...but even less people will understand why that works (it effectively desugars as the fix above).


If your actual structure is not covariant, it can't work because the caller chooses the lifetime, and callers can't name/choose lifetimes shorter than your function body. So it would be impossible to borrow the captured p for long enough in the closure body, just like you can't borrow a local variable for as long as some caller-chosen lifetime in a function body.

2 Likes

This works perfectly! I will need to take deep dive into Variance to understand this. Thanks!

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.