Borrowing

Borrowing a value essentially works like references in other languages, but with the ownership concept applied to it. Using references as function parameters still transfers ownership to the function, but instead of cleaning up when its scope ends, it returns ownership to the caller, thus the name "borrow". The ownership rule applies to object methods too, so if the signature shows self instead of a borrow like &self, the method will consume the object and it can't be used afterwards. This is used by the type conversion traits or pushing onto a vector, by example.

As borrows are part of the ownership system, there are some constraints applied to how borrows can be created and used:

  • A resource can't be moved or consumed if borrows of it exist.
  • There can only be at most one mutable borrow to a resource.
  • Immutable borrows are only possible if there is no mutable borrow on the resource, but there can be any number of them.

As consequence, a resource can't be mutated while something holds an immutable reference to it. This can lead to a programming experience commonly called "fighting the borrow checker". You should exercise extra attention to these rules when programming in Rust, but we're also going through a few examples where you might run into problems.

First off, here is an example of why the rules with mutable and immutable borrows exist:


#![allow(unused)]
fn main() {
let mut vec = vec![42];

// Borrow into a vector means
// borrowing the entire vector
let reference = &vec[0];

// Compiler error, mutable borrow!
vec.push(69);

println!("{}", *reference);
}

Why shouldn't you be allowed to add another element to the vector, when you're only holding a reference to a seemingly unrelated field of it? Well, because it's not actually independent! If you add an element to the vector, it might need to grow, triggering a reallocation. If this happens, all the elements are copied to the newly allocated memory, meaning their address in memory will change. And guess what, references are just fancy pointers, so they would end up pointing to invalid memory.

Now for a more lengthy example, this tries to replicate a common "fighting the borrow checker" moment one might have.


#![allow(unused)]
fn main() {
#[derive(Clone)]
struct MyUnit {
  pub player_id: usize,
  pub life_points: isize,
  pub damage: isize,
}

fn setup() -> Vec<MyUnit> {
  let mut units = Vec::new();
  
  for _ in 0..10 {
    units.push(MyUnit {
      player_id: 0,
      life_points: 100,
      damage: 1,
    });
    units.push(MyUnit {
      player_id: 1,
      life_points: 100,
      damage: 1,
    });
  }
  
  units
}

fn example(mut units: Vec<MyUnit>) {
  // Each shot hits all enemies for simplicity
  for my_unit in units.iter() {
    // This will not compile: trying to mutably borrow
    // the vector when we're already borrowing it in
    // the outer loop!
    for other_unit in units.iter_mut() {
      if other_unit.player_id != my_unit.player_id {
        other_unit.life_points -= my_unit.damage;
      }
    }
  }
}
}

The outer loop borrows the vector, while the inner loop tries to borrow the vector at the same time. Rust doesn't allow this. Now how would one go about fixing this problem?

The idiomatic way would be to change your structures. You could either split the vector per team, so you will borrow different elements. Another way would be to save shots into their own vector, rather than directly applying damage, then iterating through that using a second pass. Finally, you can use reference arrays per team, indexing into your main vector. However, this can reintroduce the same problem, since you want to select a team and then iterate through all other teams.

If you don't need your code to be idiomatic, you can always just use classic for-loops. This greatly reduces the scope needed for borrows, but is a bit less ergonomic to read/write and a bit easier to mess up.

struct MyUnit { pub player_id: usize, pub life_points: usize, pub damage: usize }

struct Shot {
  pub player_id: usize,
  pub damage: usize,
}

// Still room for improvement, but iterators
// are a topic for the next chapter!
fn example_idiomatic(mut units: Vec<MyUnit>) {
  let mut shots = Vec::new();

  for my_unit in units.iter() {
    shots.push(Shot {
      player_id: my_unit.player_id,
      damage: my_unit.damage
    });
  }
  
  while let Some(shot) = shots.pop() {
    for my_unit in units.iter_mut() {
      if my_unit.player_id != shot.player_id {
        my_unit.life_points -= shot.damage;
      }
    }
  }
}

// Classic loops, be careful you don't actually 
// swap i and j around at the wrong occasions!
fn example_pragmatic(mut units: Vec<MyUnit>) {
  for i in 0..units.len() {
    for j in 0..units.len() {
      if units[j].player_id != units[i].player_id {
        units[j].life_points -= units[i].damage;
      }
    }
  }
}