Search code examples
rustreferencetraitsborrow-checkermutable

How to pass a struct implementing multiple traits to a function accepting those traits as &mut?


I'm dealing with a problem that can be simplified to the following code. The result is error[E0499]: cannot borrow *dog as mutable more than once at a time..

The origin of the error is clear to me, but I'm wondering - what's the correct way to solve it? Please note that Walker and Swimmer traits use a common field to reduce the remaining energy, so for me it makes sense to make Dog to implement both of them.

I'd like to keep the function move_around unchanged, since the order of walking and swimming matters for me :) Any help would be appreciated.

trait Swimmer {
    fn swim(&mut self);
}

trait Walker {
    fn walk(&mut self);
}

struct Fish {}

impl Swimmer for Fish {
    fn swim(&mut self) {}
}

struct Turtle {}

impl Walker for Turtle {
    fn walk(&mut self) {}
}

struct Dog {
    energy: usize,
}

impl Swimmer for Dog {
    fn swim(&mut self) {
        self.energy -= 1;
    }
}

impl Walker for Dog {
    fn walk(&mut self) {
        self.energy -= 1;
    }
}

fn move_around(walker: &mut dyn Walker, swimmer: &mut dyn Swimmer) {
    walker.walk();
    swimmer.swim();
}

fn main() {
    // This does not compile: error[E0499]: cannot borrow `*dog` as mutable more than once at a time
    let dog = &mut Dog { energy: 100 };
    move_around(dog, dog);

    // Works fine.
    // move_around(&mut Turtle {}, &mut Fish {});
}

One of the possible solutions I found was split borrows - that would mean adding Walker and Swimmer fields to Dog struct and passing them to the function, but it's the shared state that makes it complicated.

Edit

I guess that in this case I could either use RefCell that I'd like to avoid or add a new function accepting generics with trait bounds:

fn move_around<T: Walker + Swimmer>(entity: &mut T) {
    entity.walk();
    entity.swim();
}

Solution

  • You have several options:

    • Use generics (static dispatch) and take a single parameter that implements both Walker and Swimmer.

    • The parallel of the previous with dynamic dispatch - make a new trait that has both Walker and Swimmer as a supertrait, add a blanket implementation for everything that is implements Walker and Swimmer, and accept &mut dyn WalkerAndSwimmer. That is:

      trait WalkerAndSwimmer: Walker + Swimmer {}
      impl<T: ?Sized + Walker + Swimmer> WalkerAndSwimmer for T {}
      
      fn move_around(v: &mut dyn WalkerAndSwimmer) {
          v.walk();
          v.swim();
      }
      

      This does not preclude the usage of different types for the walker and the swimmer, just requires more boilerplate for them: define a struct that has them both as fields and forwards Walker and Swimmer to them, and pass it.

    • Take shared references and use interior mutability. There are two ways to do that. The first is to change the traits' methods to take shared &self and put the interior mutability (e.g. RefCell) inside the struct. The second is to take leave the methods as is but take &RefCell<dyn Walker> and the same for the swimmer. Each approach has advantages and disadvantages.

      In case you cannot change neither the method nor the traits (for instance, because they come from an external library), you can leave them as-is, put the RefCell in the struct, implement the traits for a shared reference to the struct (impl Walker for &'_ Dog), and pass a mutable reference to a shared reference: &mut &dog.