Mutable self while reading from owner object

I have one object that owns another. The owned object has a mutating method that depends on non-mutating methods of its owner. The architecture (simplified as much as possible) looks like this:

struct World {
    animals: Vec<Animal>,
}

impl World {
    fn feed_all(&mut self) {
        for i in 0..self.animals.len() {
            self.animals[i].feed(self);
        }
    }
}

struct Animal {
    food: f32,
}

impl Animal {
    fn inc_food(&mut self) {
        self.food += 1.0;
    }

    fn feed(&mut self, world: &World) {
        // Imagine this is a much more complex calculation, involving many
        // queries to world.animals, several loops, and a bunch of if
        // statements. In other words, something so complex it can't just
        // be moved outside feed() and pass its result in as a pre-computed value.
        for other_animal in world.animals.iter() {
            self.food += 10.0 / (other_animal.food + self.food);
        }
    }
}

fn main() {
    let mut world = World {
        animals: Vec::with_capacity(1),
    };

    world.animals.push(Animal { food: 0.0 });

    world.feed_all();
}

The above does not compile. The compiler says:

error[E0502]: cannot borrow `*self` as immutable because `self.animals` is also borrowed as mutable
 --> src/main.rs:8:34
  |
8 |             self.animals[i].feed(self);
  |             ------------         ^^^^- mutable borrow ends here
  |             |                    |
  |             |                    immutable borrow occurs here
  |             mutable borrow occurs here

I understand why that error occurs, but what is the idiomatic Rust way to do this?

Just to be clear, the example code is not real. It's meant to present the core problem as simply as possible. The real application I'm writing is much more complex and has nothing to do with animals and feeding.

Assume it is not practical to pre-compute the food value before the call to feed(). In the real app, the method that's analogous to feed() makes many calls to the World object and does a lot of complex logic with the results.


You'd want to compute the argument first in a form that doesn't alias self, then pass that in. As it stands, it seems a little strange that an animal decides how much food it's going to eat by looking at every other animal... regardless, you could add a method Animal::decide_feed_amount(&self, world: &World) -> f32. You can call that safely (&self and &World are both immutable, so that's OK), store the result in a variable, then pass that to Animal::feed.

Edit to address your Edit: well, you're kinda screwed, then. Rust's borrow checker is not sophisticated enough to prove that the mutations you make to the Animal cannot possibly interfere with any possible immutable access to the containing World. Some things you can try:

  • Do a functional-style update. Make a copy of the Animal you want to update so that it has its own lifetime, update it, then overwrite the original. If you duplicate the whole array up front, this gives you what is effectively an atomic update of the whole array.

    As someone who worked on a simulator for like half a decade, I wish I'd done something like that instead of mutating updates. sigh

  • Change to Vec<Option<Animal>> which will allow you to move (not copy) an Animal out of the array, mutate it, then put it back (see std::mem::replace). Downside is that now everything has to check to see if there's an animal in each position of the array.

  • Put the Animals inside Cells or RefCells, which will allow you to mutate them from immutable references. It does this by performing dynamic borrow checking which is infinitely slower (no checks vs. some checks), but is still "safe".

  • Absolute last resort: unsafe. But really, if you do that, you're throwing all your memory safety guarantees out the window, so I wouldn't recommend it.


In summary: Rust is doing the right thing by refusing to compile what I wrote. There's no way to know at compile time that I won't invalidate the data I'm using. If I get a mutable pointer to one animal, the compiler can't know that my read-only access to the vector isn't invalidated by my mutations to that particular animal.

Because this can't be determined at compile time, we need some kind of runtime check, or we need to use unsafe operations to bypass the safety checks altogether.

RefCell is the way to go if we want safety at the cost of runtime checks. UnsafeCell is at least one option to solve this without the overhead, at the cost of safety of course.

I've concluded that RefCell is preferable in most cases. The overhead should be minimal. That's especially true if we're doing anything even moderately complex with the values once we obtain them: The cost of the useful operations will dwarf the cost of RefCell's checks. While UnsafeCell might be a little faster, it invites us to make mistakes.

Below is an example program solving this class of problem with RefCell. Instead of animals and feeding, I chose players, walls, and collision detection. Different scenery, same idea. This solution is generalizable to a lot of very common problems in game programming. For example:

  • A map composed of 2D tiles, where the render state of each tile depends on its neighbors. E.g. grass next to water needs to render a coast texture. The render state of a given tile updates when that tile or any of its neighbors changes.

  • An AI declares war against the player if any of the AI's allies are at war with the player.

  • A chunk of terrain is calculating its vertex normals, and it needs to know the vertex positions of the neighboring chunks.

Anyway, here's my example code:

use std::cell::RefCell;

struct Vector2 {x: f32, y: f32}

impl Vector2 {
    fn add(&self, other: &Vector2) -> Vector2 {
        Vector2 {x: self.x + other.x, y: self.y + other.y}
    }
}

struct World {
    players: Vec<RefCell<Player>>,
    walls: Vec<Wall>
}

struct Wall;

impl Wall {
    fn intersects_line_segment(&self, start: &Vector2, stop: &Vector2) -> bool {
        // Pretend this actually does a computation.
        false
    }
}

struct Player {position: Vector2, velocity: Vector2}

impl Player {
    fn collides_with_anything(&self, world: &World, start: &Vector2, stop: &Vector2) -> bool {
        for wall in world.walls.iter() {
            if wall.intersects_line_segment(start, stop) {
                return true;
            }
        }

        for cell in world.players.iter() {
            match cell.try_borrow_mut() {
                Some(player) => {
                  if player.intersects_line_segment(start, stop) {
                      return true;
                  }
                },
                // We don't want to collision detect against this player. Nor can we,
                // because we've already mutably borrowed this player. So its RefCell
                // will return None.
                None => {}
            }
        }

        false
    }

    fn intersects_line_segment(&self, start: &Vector2, stop: &Vector2) -> bool {
        // Pretend this actually does a computation.
        false
    }

    fn update_position(&mut self, world: &World) {
        let new_position = self.position.add(&self.velocity);
        if !Player::collides_with_anything(self, world, &self.position, &new_position) {
            self.position = new_position;
        }
    }
}

fn main() {
    let world = World {
        players: vec!(
            RefCell::new(
              Player {
                  position: Vector2 { x: 0.0, y: 0.0},
                  velocity: Vector2 { x: 1.0, y: 1.0}
              }
            ),
            RefCell::new(
              Player {
                  position: Vector2 { x: 1.1, y: 1.0},
                  velocity: Vector2 { x: 0.0, y: 0.0}
              }
            )
        ),

        walls: vec!(Wall, Wall)
    };

    for cell in world.players.iter() {
        let player = &mut cell.borrow_mut();
        player.update_position(&world);
    }
}

The above could be altered to use UnsafeCell with very few changes. But again,I think RefCell is preferable in this case and in most others.

Thanks to @DK for putting me on the right track to this solution.