参考字段调用回调

问题描述 投票:0回答:1

考虑这样的代码:

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
1个回答
4
投票

Rust的借用检查器的一个重要规则是,可变访问是独占访问。

在变体2中,这个规则得到支持,因为对self.fieldmut self.cbs的引用从未真正重叠。 for循环隐含地在into_iter上调用&mut Vec,它返回引用向量的std::slice::IterMut对象,但不返回Foo的其余部分。换句话说,for循环并不真正包含self的可变借用。

在变体1中,有一个call_callbacks确实保留了对自我的可变借用,这意味着它不能(直接间接地)接收另一个自我借用。换句话说,同时:

  1. 它接受对self的可变引用,允许它修改其所有字段,包括self.field
  2. 它接受一个也引用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);
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.