考虑这样的代码:
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);
}
}
}
如果我评论变体2并取消注释变体1,它不会编译,因为我需要同时使用&Foo
和&mut Foo
。
但我真的需要这个地方的功能,因为我需要相同的代码来在几个地方调用回调。
那么我在这里需要宏来调用回调,还是可能是另一种解决方案?
附注:在实际代码中我使用大结构而不是i32
,所以我无法复制它。我在OnUpdate
有几种方法,所以我需要FnMut
中的call_callbacks
。
Rust的借用检查器的一个重要规则是,可变访问是独占访问。
在变体2中,这个规则得到支持,因为对self.field
和mut self.cbs
的引用从未真正重叠。 for
循环隐含地在into_iter
上调用&mut Vec
,它返回引用向量的std::slice::IterMut
对象,但不返回Foo
的其余部分。换句话说,for
循环并不真正包含self
的可变借用。
在变体1中,有一个call_callbacks
确实保留了对自我的可变借用,这意味着它不能(直接间接地)接收另一个自我借用。换句话说,同时:
self
的可变引用,允许它修改其所有字段,包括self.field
。self
的闭包,因为它使用表达式self.field
。让这个编译将允许call_callbacks
变异self.field
而没有关闭意识到它。在整数的情况下,它可能听起来不是什么大问题,但对于其他数据,这将导致Rust的借用检查器明确设计为防止的错误。例如,Rust依赖于这些属性来防止在多线程程序中对变容器或数据争用进行不安全的迭代。
在您的情况下,可以直接避免上述情况。 set_x
控制关闭和self.field
突变的内容。可以重新设置将临时变量传递给闭包,然后更新self.field
,如下所示:
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对此代码没有任何问题,效果也是一样的。
作为练习,可以编写一个与变体2类似的call_callbacks
版本。在这种情况下,它需要接受cbs Vec
中的迭代器,就像for
循环一样,并且它一定不能接受&self
:
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);
}
}