Move callback to box, again :(


#1

I certainly some time ago solve similar problem, but my google kung fu not helps today,
and I again completly not understand what is going on:

trait Foo {
    fn on_update(&mut self);
}

#[derive(Default)]
struct Boo {
    x: i32,
    cb: Option<Box<Foo>>,
}

impl Boo {
    fn subscribe<T: Foo>(&mut self, cb: T) {
        self.cb = Some(Box::new(cb));
    }
    fn set_x(&mut self, x: i32) {
        self.x = x;
        self.cb.as_mut().map(|x| x.on_update());
    }
}

compiler what that T: Foo should be 'static, but why,
I get it by value, so why lifetime matter?


#2

So what’s happening here is default object bounds, which mean your Boo struct actually looks like:

struct Boo {
    x: i32,
    cb: Option<Box<Foo + 'static>>,
}

Why is this a thing? Because even though you’re taking T by value in subscribe that doesn’t actually mean T is a value. For instance, you might have something like:

impl<'a> Foo for &'a mut SomeStruct {
    ...
}

let foo = SomeStruct::new();

// We're not actually taking ownership of `foo` here, just some borrowed reference
boo.subscribe(&mut foo);

It’s kind of crazy when you think about the fact that T isn’t necessarily an owned structure, but it means you can be generic over ownership so long as the required lifetimes are satisfied.

So if you want to only accept really, truly owned (or statically borrowed) values then adding a 'static bound to subscribe is the right way to go.

Does that make sense?


#3

Does that make sense?

Great thanks


#4

So if you want to only accept really, truly owned (or statically borrowed) values then adding a 'static bound to subscribe is the right way to go.

you can also be more permissive and mark the Foo lifetime to be larger or equal to enclosing Boo

trait Foo {
    fn on_update(&mut self);
}

#[derive(Default)]
struct Boo<'a> {
    x: i32,
    cb: Option<Box<Foo + 'a>>,
}

impl<'a> Boo<'a> {
    fn subscribe<T: Foo + 'a>(&mut self, cb: T) {
        self.cb = Some(Box::new(cb));
    }
    fn set_x(&mut self, x: i32) {
        self.x = x;
        self.cb.as_mut().map(|x| x.on_update());
    }
}

#5

You can do that to get more flexibility in a singlethread/stack scenario. But, lifetime parameters generally make the type harder to use and less ergonomic. It also makes it impossible to move across threads unless you have Boo<'static>, at which point the lifetime parameter is noise. Crates like crossbeam could still allow an arbitrary 'a but most other code will want 'static types to be able to move across threads.

Just my $.02