Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mutating one field while iterating over another immutable field

Tags:

rust

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

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\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

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.

like image 676
Jimmy Avatar asked Mar 11 '16 09:03

Jimmy


3 Answers

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

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.

like image 119
Sebastian Redl Avatar answered Dec 11 '22 09:12

Sebastian Redl


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.

Then it's impossible.


The compiler recognizes access to different fields when it sees such fields directly; it does not break abstraction boundaries to peek inside the functions called.

There have been discussions about adding attributes on the methods to specifically mention which field is accessed by which method:

  • the compiler would enforce that a method does not touch any field NOT mentioned in the attribute
  • the compiler could then use the knowledge that said method only operates on a subset of the fields

however... this is for non-virtual methods.

For a trait this gets significantly more complicated because a trait does not have fields, and each implementer may have a different set of fields!


So now what?

You will need to change your code:

  • you can split the trait in two, and require two objects (one to iterate, one to mutate)
  • you can "hide" the mutability of the append method, forcing users to use interior mutability
  • ...
like image 27
Matthieu M. Avatar answered Dec 11 '22 11:12

Matthieu M.


You can use RefCell:

RefCell uses Rust's lifetimes to implement 'dynamic borrowing', a process whereby one can claim temporary, exclusive, mutable access to the inner value. Borrows for RefCells are tracked 'at runtime', unlike Rust's native reference types which are entirely tracked statically, at compile time. Because RefCell borrows are dynamic it is possible to attempt to borrow a value that is already mutably borrowed; when this happens it results in thread panic.

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

like image 36
aSpex Avatar answered Dec 11 '22 10:12

aSpex