Yet another lifetimes and closures related issue

Newbie here, I was toying around with the possibility of leveraging closures to get captured references but unfortunately this error came up and it very baffles me. I have the feeling the error is due to my poor understanding of how closures and/or lifetimes work, but I can't tell for sure. Could someone be so kind to enlighten me?

use std::ops::Add;

pub struct Operand<'a> {
    data: i32,
    parents_fn: Option<Box<dyn FnMut() -> (&'a Operand<'a>, &'a Operand<'a>) + 'a>>,
}

impl<'a> Add<&'a Operand<'a>> for &'a Operand<'a> {
    type Output = Operand<'a>;
    fn add(self, rhs: &'a Operand<'a>) -> Operand<'a> {
        Operand {
            data: &self.data + &rhs.data,
            parents_fn: Some(Box::new(move || (self, rhs))),
        }
    }
}

fn main() {
    let x = Operand {
        data: 1,
        parents_fn: None,
    };
    let y = Operand {
        data: 1,
        parents_fn: None,
    };
    let z = &x + &y;
}

(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
warning: unused variable: `z`
  --> src/main.rs:23:9
   |
23 |     let z = &x+&y;
   |         ^ help: if this is intentional, prefix it with an underscore: `_z`
   |
   = note: `#[warn(unused_variables)]` on by default

error[E0597]: `x` does not live long enough
  --> src/main.rs:23:13
   |
23 |     let z = &x+&y;
   |             ^^ borrowed value does not live long enough
24 | }
   | -
   | |
   | `x` dropped here while still borrowed
   | borrow might be used here, when `x` is dropped and runs the destructor for type `Operand<'_>`

error[E0597]: `y` does not live long enough
  --> src/main.rs:23:16
   |
23 |     let z = &x+&y;
   |                ^^ borrowed value does not live long enough
24 | }
   | -
   | |
   | `y` dropped here while still borrowed
   | borrow might be used here, when `x` is dropped and runs the destructor for type `Operand<'_>`
   |
   = note: values in a scope are dropped in the opposite order they are defined

error: aborting due to 2 previous errors; 1 warning emitted

For more information about this error, try `rustc --explain E0597`.
error: could not compile `playground`

To learn more, run the command again with --verbose.

The problem here is because you declared a single lifetime parameter, and used it everywhere. So Rust thinks that x or y could hold a reference to themselves, in particular inside of x.parents_fn or y.parents_fn. But this would dangle when Box<dyn FnMut(...) -> ...> drops, which would be very bad. So it fails to compile. The only way around this is to either

  1. leak memory, which can be problematic playground
  2. use only owned values. playground
    • You can combine this with Rc or Arc to cheaply share Operand

Whoops, I just realized that I didn't actually post links to working examples, fixed now

1 Like

Thanks for the tips, would you be so kind to provide some code for the latter solution? It'd be awesome as I never used Rc nor Arc

You can replace Box with Rc/Arc which are reference counted pointers. Arc is the thread-safe version of Rc. After the replacement, you can #[derive(Clone)] Operand, and then clone your operand as needed

1 Like