Scoped lifetime of callback


#1

I’m trying to model a callback whose lifetime is the same as the containing struct.

I can make this code work using Rc<RefCell<...>> but it seems it ought to be possible to have the lifetime of the callback just match the lifetime of the Child. Is it?

Currently it fails on error: p does not live long enough when I create the closure. (If you have tips on how to debug which lifetime which piece is getting, that’d be useful too.)

struct Parent<'a> {
    child: Option<Child<'a>>,
}

struct Child<'a> {
    cb: Box<FnMut() + 'a>,
}

impl<'a> Parent<'a> {
    fn non_mut(&self) -> usize {
        return 3;  // just an example of a callbackable thing
    }
}

impl<'a> Child<'a> {
    fn go(&mut self) {
        println!("child go");
        (self.cb)();
    }
}

fn main() {
    let mut p = Parent { child: None };
    let mut c = Child {
        cb: Box::new(|| {
            p.non_mut();
        }),
    };
    p.child = Some(c);
}

#2

Could you post the Rc<RefCell<...>> version as well?

I think the problem here is not about the “lifetime of callback” but that you are trying to make cyclic structure: Parent contains child while in the callback of child it try to refer to the parent.

So in your case, either comment out p.non_mut(); or p.child = Some(c); will compile. Let’s look more closely to the lifetime:

fn main() {
    let mut p = Parent { child: None }; // -+ p
    let c = Child {                     //  |
        cb: Box::new(|| {               //  | -+ c
            p.non_mut();  // -> c <= p  //  |  |
        }),                             //  |  |
    };                                  //  |  |
    p.child = Some(c);    // -> p <= c  //  |  |
}
  1. with p.non_mut(); inside the closure, we know that p must outlive the closure.
  2. with p.child = Some(c); we know that the closure have to outlive p.
  3. Thus we conclude that c == p: the closure and p should have the same lifetime. Which is not true.

Note that I am NOT absolute sure about what I said, please correct me if it is wrong. In addition, I have no idea why the error will be given for the creation of the closure, not the assignment to p.child.


#3

The problem might be that because the closure is FnMut, it takes a reference to its environment. This means that if you return from main, the closure would have a reference to p, which no longer exists. This is probably why the error appears on the creation of the closure.

Using move should solve that, but incidentally it would also prevent you from assigning p.child directly.


#4

Version using Rc/RefCell. Had to change some bits. This also creates a reference loop which can be solved with Weak but this at least shows the idea.
https://is.gd/XH1KSM

(To restate, my original question is how to make a pattern like this work without Rc.)


#5

My intent was that the closure lives as long as Child (which is why I used FnMut() + 'a in its definition), which is owned by Parent, so the lifetimes are enclosed.


#6

In this point, I think your code had already achieved it.

It is and already done. But since you are trying to take reference to the parent p, it will cause cyclic reference which cannot be done without the help of Rc or Weak.

As far as I know, you can’t(Of course without Weak either)

Then why would Rc or Weak work? That’s because with the help of Rc:

  1. we can have two or more variables refer to the same memory location.
  2. And as we know, lifetimes are bound to variables instead of the real location, that means we can have two or more lifetime for a memory location.
  3. so that we can bypass the lifetime check(e.g. p == c) which would be impossible otherwise as in your first post.