2 mutable references from struct

I have a struct which owns a data structure and a strategy to manipulate that data. In code this looks like this:

trait MyStrategy<T> {
	fn do_something(&mut self, data: &mut T);
}

struct Foo<T, B: MyStrategy<T>> {
	data: T,
	strat: B,
}

impl<T, B: MyStrategy<T>> Foo<T, B> {
	fn get_data(&mut self) -> &mut T {
		&mut self.data
	}
	fn do_something(&mut self) {
		let data = self.get_data();
		self.strat.do_something(data);
		// error[E0499]: cannot borrow `self.strat` as mutable more than once at a time
	}
}

Simply inlining get_data makes everything work again.

	fn do_something(&mut self) {
		self.behavior.do_something(&mut self.data);
		// no problem but only possible because this is a simplified example
	}

What can I do to tell the borrow checker what's going on?

I'm pretty new to Rust (3-days-new) but I think the reason the inlined version works is because Rust is able to see that I'm using disjoint parts of the struct, so it can basically treat self.behavior and self.data as two distinct variables. Rust is totally ok with two distinct &mut variables, so everything works. But when I use the getter, Rust only sees two uses of &mut self and that's a big no-no.

I guess one possible solution is to wrap data: T into its own struct and have my getter logic (and some more) there. However, this makes composition a lot more tedious to use. So is there some way to tell Rust: "Hey, I'm only using some (== not all) fields in this method"?

1 Like

Not really. You can't pass overlapping mutable references to functions, and there's nothing you can do about that.

Your analysis is spot on. Within a function, the compiler is able to see that two fields are distinct and let you mutate them both at the same time, however this analysis is deliberately restricted to within the function.

In theory we could change the borrow checker to inspect the body of all the functions that are called, and allow these sorts of borrows. The problem then is that changing a function's body (but leaving the signature the same) could break downstream code because it's now modifying different fields.

You can see how this can not only make backwards compatibility a pain, but it also means when you're writing code you need to read a function's source code and keep a lot more information in your head (the buzz word is "global reasoning"). Think global variables, but for the borrow checker.

You'd be surprised how little you hit this problem in the wild. One thing to keep in mind is that Rust code doesn't rely on getters and setters nearly as much as other languages like C# or C++.

  • The privacy rules mean anything within the same module (i.e. the same file) can refer to a struct fields directly
  • The borrow checker also makes you more aware of when things are being mutated, so having to write code that "controls" when something is mutated isn't as necessary
  • Returning a &mut T gives you pretty much free reign to mutate that field anyway, up to and including swapping it out entirely (see std::mem::replace())
  • If people are going to be mutating this field anyway, why not just make it public?
5 Likes

Thank you for the replay @Michael-F-Bryan. I marked your post as the answer since you gave a good explanation of why I can't tell the borrow checker what's going on.

That being said, some afterthoughts:

I should probably point out the above example is merely the simplest example I could think of that has this borrow checker issue. The actual "getter" in my code also takes some arguments and only returns some part of my data field. I can't really access my data field directly because that would mean duplicating the "getter" logic all over the place (I mean, I could but please don't make me).

You'd be surprised how little you hit this problem in the wild.

From what I see, this will pop up every time you try to customize a behavior using the strategy pattern. If your code is simple enough that you can just access all fields you need directly, this won't be a problem but from my non-rust experience, that's rarely the case. (I mean, I use the strategy pattern because what I'm doing isn't trivial.)

The only general way I see to apply the strategy pattern after I already defined the interface of my API seems to be to wrap all existing fields into a new struct like this:

// before
pub struct Foo { a: A, b: B, c: C }
impl Foo { 
  pub fn new() -> Self;
  fn d(&mut self) -> D; 
  pub fn e(&mut self) -> E; 
  pub fn f(&mut self) -> F; 
}

// after
// (this time, dynamic dispatch to keep the API stable)
// (I hope, I did that right)
struct FooData { a: A, b: B, c: C }
impl FooData {
  // move everything from Foo here
  pub fn new() -> Self;
  fn d(&mut self) -> D; 
  pub fn e(&mut self) -> E; 
  // the only thing that need the behavior
  pub fn f(&mut self, strat: &mut dyn Strat) -> F; 
}

pub struct Foo { data: FooData, strat: Box<dyn Strat> }
impl Foo { 
  fn new() -> Self {
    Self::new_with_strat(some_default()) 
  }
  fn new_with_strat(start: Box<dyn Strat>) -> Self {
    Foo { data: FooData::new(), strat }
  }
  pub fn e(&mut self) -> E { self.data.e() }
  pub fn f(&mut self) -> F {
    // borrow checker won't complain
    self.data.f(&mut self.strat)
  }
}

I technically only need to wrap fields used in f (and any other function that uses the behavior), so I might be able to move less code.
But still: This is the only general solution I see and it basically constitutes writing a wrapper around my API.

Please tell me there's an easier solution.

I can't see the Strat trait in that previous example, would you be able to put the code you're trying to write on the playground and share it here?

That may make it easier for us to play around with the code and try to find a pattern that works for you.

If the f function is using data in the strat field, surely it could access that field itself, instead of having it passed as an argument?

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.