1

I have this code:

struct Player {}

impl Player {
    fn update(&mut self) {}
}

struct GameBoard {
    p1: Player,
    p2: Player,
}

impl GameBoard {
    fn update(&mut self) {}
    fn update_current_player(&mut self, p1_turn: bool) {
        if p1_turn {
            self.p1.update()
        } else {
            self.p2.update();
        }

        self.update();

        if p1_turn {
            self.p1.update()
        } else {
            self.p2.update();
        }
    }
}

I'd like to not type out the if statement every time I want to update the current player. In other languages I've used, I would write something like this:

struct Player {}

impl Player {
    fn update(&mut self) {}
}

struct GameBoard {
    p1: Player,
    p2: Player,
}

impl GameBoard {
    fn update(&mut self) {}
    fn update_current_player(&mut self, p1_turn: bool) {
        let p = if p1_turn { &mut self.p1 } else { &mut self.p2 };
        p.update();

        self.update();

        p.update();
    }
}

But this annoys the borrow checker:

error[E0499]: cannot borrow `*self` as mutable more than once at a time
  --> src/lib.rs:18:9
   |
15 |         let p = if p1_turn { &mut self.p1 } else { &mut self.p2 };
   |                                                    ------------ first mutable borrow occurs here
...
18 |         self.update();
   |         ^^^^ second mutable borrow occurs here
19 | 
20 |         p.update();
   |         - first borrow later used here

There must be some way not to have to type out the if statement every time. I would also prefer not to break the if statement out into a function that takes p1_turn, because writing out the function call is still fairly long. Does anyone have suggestions?

I understand why this pattern is unsafe if you're accessing data inside an Enum or on the heap, since the call to self.update() might invalidate the reference, but in this case the reference can't possibly be made invalid by any code in self.update() because it's just referring to a field of the struct.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
mlucy
  • 5,249
  • 1
  • 17
  • 21
  • 1
    `enum`s and pointers aren't the only reason why this is unsafe. The compiler is permitted to optimize `update_current_player` by assuming that `p` is not aliased during its lifetime, which might include omitting redundant loads and stores -- instructions that would become very *not* redundant if another reference (`self`) is being used concurrently to access the same data. You can opt out of those optimizations, using one of the "interior mutability" types (`Cell`, `RefCell`, `Mutex`, `RwLock` etc.) – trent Feb 02 '21 at 00:12

1 Answers1

3

There must be some way not to have to type out the if statement every time. I would also prefer not to break the if statement out into a function that takes p1_turn, because writing out the function call is still fairly long.

Given those requirements you can create an anonymous closure which captures the p1_turn variable and re-use that. However, to avoid overlapping mutable borrows you'd have to pass &mut self as an argument to the closure. Example:

struct Player {}

impl Player {
    fn update(&mut self) {}
}

struct GameBoard {
    p1: Player,
    p2: Player,
}

impl GameBoard {
    fn update(&mut self) {}
    fn update_current_player(&mut self, p1_turn: bool) {
        let p_update = |gb: &mut GameBoard| {
            if p1_turn {
                gb.p1.update();
            } else {
                gb.p2.update();
            }
        };

        p_update(self);
        self.update();
        p_update(self);
    }
}

playground

pretzelhammer
  • 13,874
  • 15
  • 47
  • 98