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
.
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:
It accepts a mutable reference to self
, which allows it to modify all its fields, including self.field
.
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);
}
}