【问题标题】:Iterating over a vector of mutable references to trait objects迭代特征对象的可变引用向量
【发布时间】:2016-08-27 17:55:43
【问题描述】:

我有一个 struct 持有对 trait 对象的可变引用:

trait Task {
    fn do_it(&mut self);
}

struct Worker<'a> {
    tasks: Vec<&'a mut Task>,
}

Worker的方法中,我想遍历任务并调用它们的do_it

impl<'a> Worker<'a> {
    pub fn work(&mut self) {
        for task in self.tasks.iter() {
            self.work_one(*task);
        }
    }

    fn work_one(&self, task: &mut Task) {
        task.do_it();
    }
}

遗憾的是,借阅检查器不允许我这样做:

error[E0389]: cannot borrow data mutably in a `&` reference
  --> src/main.rs:12:27
   |
12 |             self.work_one(*task);
   |                           ^^^^^ assignment into an immutable reference

我不能将Worker 设为通用,因为我希望它能够容纳多种类型的任务。我还需要任务是可变的。我如何在 Rust 中做到这一点?

【问题讨论】:

    标签: rust borrow-checker


    【解决方案1】:

    您正在调用tasks.iter(),它会生成对Vec 元素的不可变引用。你实际上得到了&amp;&amp;mut Task,一个对可变引用的不可变引用(这就是 Rust 编译器抱怨的原因)。

    要解决这个问题,请调用 tasks.iter_mut() 以获取可变引用的迭代器。

    第二个问题是调用将work_one 定义为方法。迭代时你已经从self借了一个可变引用,所以你不能再借。

    工作示例 (playground):

    trait Task {
        fn do_it(&mut self);
    }
    
    struct Worker<'a> {
        tasks: Vec<&'a mut Task>,
    }
    
    impl<'a> Worker<'a> {
        pub fn work(&mut self) {
            for task in self.tasks.iter_mut() {
                Worker::work_one(*task);
            }
        }
    
        fn work_one(task: &mut Task) {
            task.do_it();
        }
    }
    

    要在work_one 中仍然可以访问self,可以使用此解决方法。这基本上只是交换了两个向量,因此在迭代然后将其交换回来时,您实际上并没有借用 self。这很难看,这里可能有更好的模式,也许其他人会提出更好的建议。

    pub fn work(&mut self) {
        let mut tasks = vec![];
        mem::swap(&mut tasks, &mut self.tasks);
        for task in tasks.iter_mut() {
            self.work_one(*task);
        }
        mem::swap(&mut tasks, &mut self.tasks);
    }
    

    @Veedrac 提出的更好的替代方案:

    fn work(&mut self) {
        let mut tasks = mem::replace(&mut self.tasks, Vec::new());
        for task in &mut tasks {
            self.work_one(*task);
        }
        self.tasks = tasks;
    }
    

    【讨论】:

    • “对可变引用的不可变引用”啊啊,这是我错过的一块。关于对 self 的引用:这意味着如果我需要对 self 的状态进行任何更改,我需要使用 for 循环的主体来完成。我猜对了吗?
    • @Tomo 更新了答案,并不是一个理想的解决方案。希望其他人可以提出更好的建议。
    • 是的,这看起来很难看。 :)
    • @Tomo 除非您对问题更具体,否则您可能无法做得更好。如果您可变地借出self,则表示您允许进行无限制的修改。也就是说,it can be made prettier than two swaps.
    • @Veedrac 我不知道是否需要修改Worker 的状态。我好奇地问道。感谢mem::replace 的技巧,很高兴能把它放在我的工具箱里。
    【解决方案2】:

    您需要对每个项目都有一个可变引用。 iter 返回不可变引用。对可变变量的不可变引用本身不是可变的。请改用iter_mutfor task in &amp;mut self.tasks

    然后,最简单的事情就是将work_one 内联到work

    pub fn work(&mut self) {
        for task in self.tasks.iter_mut() {
            task.do_it()
        }
    }
    

    不幸的是,把它分成两个函数是相当痛苦的。您必须保证调用self.work_one 不会修改self.tasks。 Rust 不会跨函数边界跟踪这些东西,因此您需要拆分所有其他成员变量并将它们分别传递给函数。

    另见:

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-10-23
      • 2018-02-14
      • 2013-08-03
      • 1970-01-01
      • 1970-01-01
      • 2016-08-17
      相关资源
      最近更新 更多