Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
1.5k views
in Technique[技术] by (71.8m points)

rust - Mutating one field while iterating over another immutable field

Given the following program:

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
"),
                _ => self.append("it was something else
"),
            }
        }
    }

    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);
}

The following errors are produced trying to compile it:

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
"),
   |                          ^^^^ 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
"),
   |                      ^^^^ mutable borrow occurs here

What is the proper way to structure the code so that the mutable field output can be written to while iterating over the immutable field data? Assume the indirection through the Generator trait is being used to share similar logic with other structs, so accessing MyStruct's fields from the trait's default method implementations need to be done through accessor methods like this.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

This is a common issue in Rust; the typical way of solving it is the replace dance. This involves making more of the data and methods use mutable references:

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
"),
                _ => self.append("it was something else
"),
            }
        }
        // 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);
}

The thing to realize is that the compiler is right to complain. Imagine if calling output() had the side effect of mutating the thing that is referenced by the return value of data() Then the iterator you're using in the loop could get invalidated. Your trait functions have the implicit contract that they don't do anything like that, but there is no way of checking this. So the only thing you can do is temporarily assume full control over the data, by taking it out.

Of course, this pattern breaks unwind safety; a panic in the loop will leave the data moved out.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...