Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Borrowing references to attributes in a struct

Tags:

rust

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
like image 539
Ignacio Martin Avatar asked Aug 26 '14 19:08

Ignacio Martin


People also ask

How do you borrow variables in rust?

An Example of Borrowing in Rust You can borrow the ownership of a variable by referencing the owner using the ampersand (&) symbol. Without borrowing by referencing, the program would panic. It would violate the ownership rule that a value can have one owner, and two variables cannot point to the same memory location.

What is borrowing in Rust?

What is Borrowing? When a function transfers its control over a variable/value to another function temporarily, for a while, it is called borrowing. This is achieved by passing a reference to the variable (& var_name) rather than passing the variable/value itself to the function.

What is a borrow checker?

The borrow check is Rust's "secret sauce" – it is tasked with enforcing a number of properties: That all variables are initialized before they are used. That you can't move the same value twice. That you can't move a value while it is borrowed.


2 Answers

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);
    }
    
like image 110
Levans Avatar answered Nov 15 '22 10:11

Levans


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.

like image 32
Shepmaster Avatar answered Nov 15 '22 11:11

Shepmaster