rust - 在迭代另一个不可变字段时改变一个字段

标签 rust

给定以下程序:

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 中的一个常见问题;解决它的典型方法是替换舞蹈。这涉及使更多数据和方法使用可变引用:

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() 的返回值所引用的内容,那么您在循环中使用的迭代器可能无效。你的特征函数有一个隐含的契约,即它们不会做那样的事情,但没有办法检查这一点。因此,您唯一能做的就是暂时完全控制数据,将其取出。

当然,这种模式会破坏展开安全;循环中的 panic 会使数据移出。

关于rust - 在迭代另一个不可变字段时改变一个字段,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47618162/

相关文章:

rust - "mismatched types: expected ` ( )`"在使用 if 表达式时意味着什么?

rust - 在迭代可变成员时访问不可变方法

rust - 可变地借用一个结构字段,同时在闭包中借用另一个

image - 复制/move 未实现复制的字段

rust - 特征实现大小

Rust 复合声明

rust - 通过网络发送 "application/x-www-form-urlencoded"数据代替 JSON

c++ - 是否可以将此 Rust 代码写入语义上等效的 C++ 代码?

rust - 如何使用 reqwest 执行并行异步 HTTP GET 请求?

rust - 为什么在通过 massif 执行时连接到 Rust 中的 MySQL 会崩溃?