【问题标题】:call callback with reference to field参考字段调用回调
【发布时间】:2017-09-09 21:54:39
【问题描述】:

考虑这样的代码:

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, 它无法编译,因为我同时需要 &amp;Foo&amp;mut Foo

但我真的需要这个地方的功能,因为我需要同样的功能 在多个地方调用回调的代码。

那么我这里需要宏来调用回调吗,或者可能是另一种解决方案?

旁注:在实际代码中,我使用大结构而不是i32, 所以我不能复制它。我在OnUpdate 也有几种方法, 所以我需要FnMut 中的call_callbacks

【问题讨论】:

    标签: rust


    【解决方案1】:

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

    在变体 2 中,此规则得到支持,因为对 self.fieldmut self.cbs 的引用从未真正重叠。 for 循环隐式调用&amp;mut Vec 上的into_iter,它返回一个引用vectorstd::slice::IterMut 对象,而不是Foo 的其余部分。换句话说,for 循环实际上并不包含 self 的可变借用。

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

    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 循环一样,并且它根本不能接受&amp;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);
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-09-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多