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!