Cannot borrow *self as mutable because an unrelated field is already borrowed

struct S { 
    foo: usize,
    bar: Vec<f64>,
}

I have a function update_foo() that only mutates foo. If I want to call it from another function (like update()), if the call to update_foo() is done while self.bar is already borrowed, the borrow checker complains that

error[E0502]: cannot borrow *self as mutable because it is also borrowed as immutable

Is there any better way than modifying the signature of update_foo() to take a mutable reference to the type of foo instead of taking a mutable reference to self? This works, but this feels very ugly.


Playground
Code is pasted bellow to save you a click :slight_smile:

impl S { 
    pub fn update(&mut self) {
        for bar in &self.bar { // borrow immutably self.bar
            self.update_foo(*bar as usize); // should only borrow mutably self.foo
        }   
    }   
    fn update_foo(&mut self, value: usize) {
        self.foo = value; // only touch self.foo
    }   
}

&mut self always exclusively borrows all of self, including all its fields at once. This is intentional, because functions are meant to be an abstraction boundary, and not break their callers in case they used more fields in the future. The downside is that they're inflexible like that.

Alternatives:

  1. Don't use setters, and operate on bare fields
  2. Don't use &mut self in the method, but only pass references to fields it uses.
  3. Split the data into two structs, so that foo and bar live in two different structs.
2 Likes

Here is another approach that uses a function to encapsulate the recomputation of the struct prop foo. Not necessarily the way to go, but does emphasize the idea that you can accomplish a task without always using a trait or inherent method. Viva the function! (and an FP approach).

Playground

Note: this may be what @kornel means in (1) :))

In not calling a method where &mut self is the receiver inside the iterator that immutability borrows self.prop, — but instead only requires &mut self.different_prop the compiler does not cascade the mut borrow to the already immutable borrowed self.prop; thus avoids the collision.

So, when the cascading self type creates a collision, consider going with a function. I see it as an alternative to traits for sharing code across types; types that is, that share properties/fields of the same type.

1 Like

I get the reasonning, and totally get it for public functions, but I think this restriction should be lifted for private one.

Yeah, I think there is a general agreement that it'd be good to lift it for private functions, but there hasn't been a serious attempt to spec it exactly.

1 Like

There are links here to previous discussions about how Rust may evolve to handle this situation (e.g. "fields in traits"), and you may find the rest of the article informative as well.

2 Likes

If the restriction is lifted for private functions, I think that for consistency reasons it would be a good idea to lift it for public functions at the same time. Otherwise the privacy controls suddenly would do more than just specifying privacy.

Lifting the restriction for private function doesn't have hidden semver impact (by definition), while lifting it for public function would. That's the same reason why function that could be automatically made const are not, because changing the implementation could silently change the interface and thus breaking semver.

1 Like

I haven't thought a lot about this, but lifting the restriction for private methods would seem to make reasoning about your functions more difficult, especially for refactoring purposes.

It's one thing to say adding a public function is a breaking change, but it's another thing entirely to have changing the privacy of a function to cause it to fail to compile.

It just seems inconsistent.

1 Like

I think these are both correct, and that together they imply that the correct thing to do is

  • Be explicit and not implicit
    • So that, like const, you don't accidentally promise more than you intended
  • Not gate on privacy
    • Semver isn't something for the language to enforce, and in fact it cannot, as the language itself has no concept of your crate's release version

(Even if it were gated on privacy, I would oppose an implicit version. Triggering significant changes in behaviour implicitly or at a distance (e.g. from changing a method body) is a misfeature. It's part of the method's contract; put it in the method's declaration.)

1 Like