【问题标题】:Mutating one field while iterating over another immutable field在迭代另一个不可变字段时改变一个字段
【发布时间】:2016-03-11 09:54:37
【问题描述】:

给定以下程序:

struct Data {
    pub items: Vec<&'static str>,
}

trait Generator {
    fn append(&mut self, s: &str) {
        self.output().push_str(s);
    }

    fn data(&self) -> &Data;

    fn generate_items(&mut self) {
        for item in self.data().items.iter() {
            match *item {
                "foo" => self.append("it was foo\n"),
                _ => self.append("it was something else\n"),
            }
        }
    }

    fn output(&mut self) -> &mut String;
}

struct MyGenerator<'a> {
    data: &'a Data,
    output: String,
}

impl<'a> MyGenerator<'a> {
    fn generate(mut self) -> String {
        self.generate_items();

        self.output
    }
}

impl<'a> Generator for MyGenerator<'a> {
    fn data(&self) -> &Data {
        self.data
    }

    fn output(&mut self) -> &mut String {
        &mut self.output
    }
}

fn main() {
    let data = Data {
        items: vec!["foo", "bar", "baz"],
    };

    let generator = MyGenerator {
        data: &data,
        output: String::new(),
    };

    let output = generator.generate();

    println!("{}", output);
}

试图编译它会产生以下错误:

error[E0502]: cannot borrow `*self` as mutable because it is also borrowed as immutable
  --> src/main.rs:15:26
   |
13 |         for item in self.data().items.iter() {
   |                     ----                   - immutable borrow ends here
   |                     |
   |                     immutable borrow occurs here
14 |             match *item {
15 |                 "foo" => self.append("it was foo\n"),
   |                          ^^^^ mutable borrow occurs here

error[E0502]: cannot borrow `*self` as mutable because it is also borrowed as immutable
  --> src/main.rs:16:22
   |
13 |         for item in self.data().items.iter() {
   |                     ----                   - immutable borrow ends here
   |                     |
   |                     immutable borrow occurs here
...
16 |                 _ => self.append("it was something else\n"),
   |                      ^^^^ mutable borrow occurs here

什么是构造代码的正确方法,以便在迭代不可变字段data 时可以写入可变字段output?假设通过Generator trait 的间接访问被用于与其他结构共享类似的逻辑,因此从 trait 的默认方法实现中访问 MyStruct 的字段需要通过这样的访问器方法来完成。

【问题讨论】:

标签: rust


【解决方案1】:

这是 Rust 中的常见问题;解决它的典型方法是替换舞蹈。这涉及使更多的数据和方法使用可变引用:

struct Data {
    pub items: Vec<&'static str>,
}

trait Generator {
    fn append(&mut self, s: &str) {
        self.output().push_str(s);
    }

    fn data(&mut self) -> &mut Data;

    fn generate_items(&mut self) {
        // Take the data. The borrow on self ends after this statement.
        let data = std::mem::replace(self.data(), Data { items: vec![] });
        // Iterate over the local version. Now append can borrow all it wants.
        for item in data.items.iter() {
            match *item {
                "foo" => self.append("it was foo\n"),
                _ => self.append("it was something else\n"),
            }
        }
        // Put the data back where it belongs.
        std::mem::replace(self.data(), data);
    }
    fn output(&mut self) -> &mut String;
}

struct MyGenerator<'a> {
    data: &'a mut Data,
    output: String,
}

impl<'a> MyGenerator<'a> {
    fn generate(mut self) -> String {
        self.generate_items();

        self.output
    }
}

impl<'a> Generator for MyGenerator<'a> {
    fn data(&mut self) -> &mut Data {
        self.data
    }

    fn output(&mut self) -> &mut String {
        &mut self.output
    }
}

fn main() {
    let mut data = Data {
        items: vec!["foo", "bar", "baz"],
    };

    let generator = MyGenerator {
        data: &mut data,
        output: String::new(),
    };

    let output = generator.generate();

    println!("{}", output);
}

要意识到编译器是正确抱怨的。想象一下,如果调用output() 具有改变data() 的返回值所引用的东西的副作用,那么您在循环中使用的迭代器可能会失效。您的特征函数具有隐式契约,即它们不会做类似的事情,但无法检查这一点。因此,您唯一能做的就是暂时完全控制数据,将其取出。

当然,这种模式破坏了展开安全性;循环中的恐慌将使数据移出。

【讨论】:

  • 一方面这需要将data 的定义更改为fn data(&amp;mut self) -&gt; &amp;mut Data(请注意,到目前为止它是不变的)。另一方面,我觉得它很优雅!
【解决方案2】:

假设通过Generator trait 的间接访问被用于与其他结构共享类似的逻辑,因此从 trait 的默认方法实现中访问 MyStruct 的字段需要通过像这样的访问器方法来完成。

那是不可能的。


编译器在看到这些字段时识别对不同字段的访问直接;它不会打破抽象边界以窥视被调用的函数。

已经讨论过在方法上添加属性以具体提及哪个字段由哪个方法访问:

  • 编译器将强制方法不触及属性中未提及的任何字段
  • 然后编译器可以使用该方法仅对字段子集进行操作的知识

但是...这是针对非虚拟方法的。

对于 trait,这会变得更加复杂,因为 trait 没有字段,并且每个实现者可能有不同的字段集!


那么现在呢?

您需要更改您的代码:

  • 您可以将特征一分为二,并需要两个对象(一个用于迭代,一个用于变异)
  • 您可以“隐藏”append 方法的可变性,强制用户使用内部可变性
  • ...

【讨论】:

  • 您对如何进行有个人偏好吗?使用RefCell?将不可变数据和输出字符串拆分为两个单独的对象?摆脱这个特征,只用一种类型来处理它?
  • @JimmyCuadra:对于这个特定的例子,我会说两个特征和两个结构......但我认为这是一个简化的例子,这就是为什么我提出了多种解决方案并让你来选择这对你来说似乎是最好的。
【解决方案3】:

你可以使用RefCell:

RefCell 使用 Rust 的生命周期来实现“动态借用”,一个 一个人可以要求临时的、排他的、可变的访问权的过程 内在价值。 RefCells 的借用在“运行时”被跟踪, 不像 Rust 的原生引用类型被完全跟踪 静态地,在编译时。因为 RefCell 借用是动态的 可以尝试借用一个已经可变的值 借来的;发生这种情况时会导致线程恐慌。

use std::cell::{RefCell, RefMut};

struct Data {
    pub items: Vec<&'static str>,
}

trait Generator {
    fn append(&self, s: &str) {
        self.output().push_str(s);
    }

    fn data(&self) -> &Data;

    fn generate_items(&self) {
        for item in self.data().items.iter() {
            match *item {
                "foo" => self.append("it was foo\n"),
                _ => self.append("it was something else\n"),
            }
        }
    }

    fn output(&self) -> RefMut<String>;
}

struct MyGenerator<'a> {
    data: &'a Data,
    output: RefCell<String>,
}

impl<'a> MyGenerator<'a> {
    fn generate(self) -> String {
        self.generate_items();

        self.output.into_inner()
    }
}

impl<'a> Generator for MyGenerator<'a> {
    fn data(&self) -> &Data {
        self.data
    }

    fn output(&self) -> RefMut<String> {
        self.output.borrow_mut()
    }
}

fn main() {
    let data = Data {
        items: vec!["foo", "bar", "baz"],
    };

    let generator = MyGenerator {
        data: &data,
        output: RefCell::new(String::new()),
    };

    let output = generator.generate();

    println!("{}", output);
}

Rust playground

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-01-15
    • 1970-01-01
    • 2019-11-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多