Borrowing references to attributes in a struct

2019-01-08 02:20发布

问题:

It seems that if you borrow a reference to a struct field, the whole struct is considered borrowed. I've managed to isolate and example of what I want to do. I just want to get a "read-only" reference to a field in B to obtain some data and then modify another field of B. Is there a idiomatic Rust way to do this?

struct A {
    i: i32,
}

struct B {
    j: i32,
    a: Box<A>,
}

impl B {
    fn get<'a>(&'a mut self) -> &'a A {
        &*self.a
    }
    fn set(&mut self, j: i32) {
        self.j = j
    }
}

fn foo(a: &A) -> i32 {
    a.i + 1
}

fn main() {
    let a = Box::new(A { i: 47 });
    let mut b = B { a: a, j: 1 };
    let a_ref = b.get();
    b.set(foo(a_ref));
}
error[E0499]: cannot borrow `b` as mutable more than once at a time
  --> src/main.rs:27:5
   |
26 |     let a_ref = b.get();
   |                 - first mutable borrow occurs here
27 |     b.set(foo(a_ref));
   |     ^ second mutable borrow occurs here
28 | }
   | - first borrow ends here

回答1:

It's a feature of the language. From the compiler point of view, there is no way for it to know that it's safe to call your set() function while a is borrowed via get().

Your get() function borrows b mutably, and returns a reference, thus b will remain borrowed until this reference goes out of scope.

You have several way of handling this:

  1. Separate your two fields into two different structs

  2. Move the code which needs to access both attribute inside a method of B

  3. Make your attributes public, you will thus be able to directly get references to them

  4. Compute the new value before setting it, like this:

    fn main() {
        let a = Box::new(A { i: 47 });
        let mut b = B { a: a, j: 1 };
        let newval = {
            let a_ref = b.get();
            foo(a_ref)
        };
        b.set(newval);
    }
    


回答2:

Expanding a bit on Levans' answer

Move the code which needs to access both attribute inside a method of B

That might look like this at a first pass:

impl B {
    fn do_thing(&mut self) {
        self.j = self.a.foo()
    }
}

However, this hard-codes the call to foo. You could also accept a closure to allow this to be more flexible:

impl B {
    fn update_j_with_a<F>(&mut self, f: F)
    where
        F: FnOnce(&mut A) -> i32,
    {
        self.j = f(&mut self.a)
    }
}

// ...

b.update_j_with_a(|a| a.foo())

Separate your two fields into two different structs

This is also applicable when you have borrowed two disjoint subsets of attributes. For example:

struct A {
    description: String,
    name: String,
    age: u8,
    money: i32,
}

impl A {
    fn update_description(&mut self) {
        let description = &mut self.description;
        *description = self.build_description()
        // cannot borrow `*self` as immutable because `self.description` is also borrowed as mutable
    }

    fn build_description(&self) -> String {
        format!(
            "{} is {} years old and has {} money",
            self.name,
            self.age,
            self.money
        )
    }
}

fn main() {}

Can be changed into

struct A {
    description: String,
    info: Info,
}

struct Info {
    name: String,
    age: u8,
    money: i32,
}

impl A {
    fn update_description(&mut self) {
        let description = &mut self.description;
        *description = self.info.build_description()
    }
}

impl Info {
    fn build_description(&self) -> String {
        format!(
            "{} is {} years old and has {} money",
            self.name,
            self.age,
            self.money
        )
    }
}

fn main() {}

You can combine these two steps (and I'd say that it's better practice) and move the method onto the inner struct.



标签: rust