call callback with reference to field

2019-08-21 21:26发布

问题:

Consider such code:

trait OnUpdate {
    fn on_update(&mut self, x: &i32);
}

struct Foo {
    field: i32,
    cbs: Vec<Box<OnUpdate>>,
}

impl Foo {
    fn subscribe(&mut self, cb: Box<OnUpdate>) {
        self.cbs.push(cb);
    }
    fn set_x(&mut self, v: i32) {
        self.field = v;

        //variant 1
        //self.call_callbacks(|v| v.on_update(&self.field));

        //variant 2
        let f_ref = &self.field;
        for item in &mut self.cbs {
            item.on_update(f_ref);
        }
    }
    fn call_callbacks<CB: FnMut(&mut Box<OnUpdate>)>(&mut self, mut cb: CB) {
        for item in &mut self.cbs {
            cb(item);
        }
    }
}

If I comment variant 2 and uncomment variant 1, it doesn't compiles, because of I need &Foo and &mut Foo at the same time.

But I really need function in this place, because of I need the same code to call callbacks in several places.

So do I need macros here to call callbacks, or may be another solution?

Side notes: in real code I use big structure instead of i32, so I can not copy it. Also I have several methods in OnUpdate, so I need FnMut in call_callbacks.

回答1:

An important rule of Rust's borrow checker is, mutable access is exclusive access.

In variant 2, this rule is upheld because the reference to self.field and to mut self.cbs never really overlap. The for loop implicitly invokes into_iter on &mut Vec, which returns a std::slice::IterMut object that references the vector, but not the rest of Foo. In other words, the for loop does not really contain a mutable borrow of self.

In variant 1, there is a call_callbacks which does retain a mutable borrow of self, which means it cannot receive (directly on indirectly) another borrow of self. In other words, at the same time:

  1. It accepts a mutable reference to self, which allows it to modify all its fields, including self.field.

  2. It accepts a closure that also refers to self, because it uses the expression self.field.

Letting this compile would allow call_callbacks to mutate self.field without the closure being aware of it. In case of an integer it might not sound like a big deal, but for other data this would lead to bugs that Rust's borrow checker is explicitly designed to prevent. For example, Rust relies on these properties to prevent unsafe iteration over mutating containers or data races in multi-threaded programs.

In your case it is straightforward to avoid the above situation. set_x is in control both of the contents of the closure and of the mutation to self.field. It could be restated to pass a temporary variable to the closure, and then update self.field, like this:

impl Foo {
    fn subscribe(&mut self, cb: Box<OnUpdate>) {
        self.cbs.push(cb);
    }
    fn set_x(&mut self, v: i32) {
        self.call_callbacks(|cb| cb.on_update(&v));
        self.field = v;
    }

    fn call_callbacks<OP>(&mut self, mut operation: OP)
        where OP: FnMut(&mut OnUpdate)
    {
        for cb in self.cbs.iter_mut() {
            operation(&mut **cb);
        }
    }
}

Rust has no problem with this code, and effect is the same.

As an exercise, it is possible to write a version of call_callbacks that works like variant 2. In that case, it needs to accept an iterator into the cbs Vec, much like the for loop does, and it must not accept &self at all:

fn set_x(&mut self, v: i32) {
    self.field = v;
    let fref = &self.field;
    Foo::call_callbacks(&mut self.cbs.iter_mut(),
                        |cb| cb.on_update(fref));
}

fn call_callbacks<OP>(it: &mut Iterator<Item=&mut Box<OnUpdate>>,
                      mut operation: OP)
    where OP: FnMut(&mut OnUpdate)
{
    for cb in it {
        operation(&mut **cb);
    }
}


标签: rust