Borrowing/References

Borrowing in Rust means obtaining a reference to some variable. From now on, we'll use phrases "obtaining a reference" and "borrowing" interchangeably. When you obtain a reference to a variable foo and assign it to another variable bar, the variable bar is the borrower and the variable foo is the owner/lender. Thus, bar refers to foo or we can say bar borrows foo's value. Variable bar can freely read the data owned by foo but it cannot modify the data unless otherwise specified via the mut keyword. (more on this below)

Usually in Rust, when you want to assign a heap allocated variable to another variable or pass it to a function, what you want is to borrow the data that variable refers to (or succinctly borrow the variable) rather than taking ownership of it as we saw in the previous section. In order to borrow a variable, you obtain a reference to that variable.

Let's use the String type as an example:


fn main() {
  let fruit = String::from("apple");

  println!("Fruit is {}", fruit);

  let fruit2 = &fruit; //borrow occurs here i.e. fruit2 is a reference to fruit

  println!("Fruit2 is {}", fruit2);
  
  //Can still use fruit variable here
  println!("Fruit is {}", fruit);
} 

The diagram below illustrates what happens when you borrow a variable:

borrowing

Similarly, for method calls, you can pass a reference as argument (i.e. borrowed the variable) to the function call like so:

fn main() {
  let name = String::from("Olu");

  println!("Name is {}", name);

  say_hello(&name); //borrow occurs here
  
  //Can still use name variable here
  println!("Name is {}", name);
} 

fn say_hello(name: &String) { //name is a reference to a String passed into the function
   println!("Hello {}", name)
}

A reference (i.e. a borrowed variable) is immutable by default

By default, when you borrow a variable, the reference is immutable. That is, by default, you cannot
use the reference to change the variable. Example, the below snippet fails:

fn main() {
  let fruit = String::from("apple");

  println!("Fruit is {}", fruit);

  let fruit2 = &fruit; //borrow occurs here i.e. fruit2 is an immutable reference to fruit
  
  println!("Fruit2 is {}", fruit2);
  
  let another_fruit = String::from("pear");
  
  fruit2 = &another_fruit; //compiler error here
  
  println!("Fruit2 is now {}", fruit2);
} 

You'll see from the error message shown that fruit2 is immutable by default, just like any other rust variable (i.e. when you do a let foo = ..., the variable, reference or not, is always immutable by default). Adding the mut keyword to fruit2 declaration sorts it:

fn main() {
  let fruit = String::from("apple");

  println!("Fruit is {}", fruit);

  let mut fruit2 = &fruit; //borrow occurs here i.e. fruit2 is a mutable reference to fruit
  
  println!("Fruit2 is {}", fruit2);
  
  let another_fruit = String::from("pear");
  
  fruit2 = &another_fruit; //Now you can assign a new reference to fruit2
  
  println!("Fruit is still {}", fruit); // still refers to the the "apple" String
  println!("Fruit2 is now {}", fruit2); //now refers to the "pear" String
} 

In the above, example, we initially assigned a reference to a String containing "apple" to fruit2 and then we assigned another reference to a different String containing "pear" to fruit. Thus we made fruit2 refer to two different Strings (one at a time) over the course of the program. What if we wanted to use fruit2 to modify the original String i.e. the String::from("apple")?

fn main() {
  let mut fruit = String::from("apple");

  println!("Fruit is {}", fruit);

  let fruit2 = & mut fruit; //borrow occurs here i.e. fruit2 is a mutable reference to fruit
  
  println!("Fruit2 is {}", fruit2);
  
  fruit2.push_str(" and pears");
  
  //Both fruit and fruit2 refer to the same `String`
  println!("Fruit2 is now {}", fruit2);
  println!("Fruit is now {}", fruit); 
} 

Temporary references

As we have seen, in order for you to borrow a variable's value (i.e. obtain a reference), you do something like:

#![allow(unused)]
fn main() {
let my_apple = String::from("apple");
let your_apple = &my_apple;
}

where my_apple is the variable you are borrowing from abd your_apple is the borrowed value (i.e., reference to my_apple's value). Thus, in order for us to borrow a variable's value, we do so via the name of the variable (in this case my_apple) whose value we want to borrow.

However, it is possible to create a borrow a value using a what I call a shorthand approach, i.e., without using an intermediate variable as shown below:

#![allow(unused)]
fn main() {
let your_apple = &String::from("apple");
}

What actually happens for the above is that Rust will create a temporary variable for us behind the scenes and it is through that temporary variable that the reference assigned to your_apple is created. That is, the snippet would essentially be expanded to what we did in the first one:

#![allow(unused)]
fn main() {
let temp_variable_created_by_rust = String::from("apple");
let your_apple = &temp_variable_created_by_rust;
}

References

References rules

  • At any given time, you can have either one mutable reference or many immutable references.
fn main() {
  let mut fruit = String::from("apple");

  println!("Fruit is {}", fruit);

  let fruit2 = & mut fruit; //borrow occurs here i.e. fruit2 is a mutable reference to fruit
  
  println!("Fruit2 is {}", fruit2);
  
  fruit2.push_str(" and pears"); //Do mutation
  
  //Both fruit and fruit2 refer to the same `String`
  //Crucially fruit2 which is a mutable reference to fruit, has exclusivity
  //i.e., we cannot have another mutable or non-mutable reference to fruit
  //while fruit2 is still in scope.
  
  //When the two lines below are uncommented, you get a compiler error
  //let fruit3 = &fruit;  
  //println!("Fruit2 is now {}", fruit2);
  
  //You also get a compiler error if say you tried to use println! to
   //print fruit before fruit2. This is because the print/println macros
   //implicitly do a borrow of the variable you're trying to print

   //println!("Fruit is now {}", fruit); //println does an implicit borrow of `fruit` so the code will fail here
   //println!("Fruit2 is now {}", fruit2); //fruit2 which is a mutable borrow and thus has exclusivity to borrowing
   
   
   //correct thing to do here is:
   println!("Fruit2 is now {}", fruit2); //fruit2 goes out of scope since it's no longer used beyond this point
   
   //it is now safe to obtain another reference e.g. explicitly or through an implicit borrow as in the case of println
   let fruit3 = &fruit;
   println!("Fruit is now {}", fruit); 
  
} 
  • References must always be valid i.e. a reference must always refer to a variable that is in scope.
    • A common example of Rust enforcing this rule is the prevention of references from being returned from a function if that reference refers to a variable created in that function. This is because that variable is part of that function's call stack and that call stack is destroyed when the function ends. Thus, if Rust allows the function to return a reference to such a variable from the function, that reference would be a dangling reference (i.e. it would be pointing to something that has been dropped) and this is a logic error. Thankfully, Rust compiler prevents you from doing this. If you create a variable that references heap allocated data (such as a String) in a function and you want to return it from that function, what you'll want to do instead is to move it. That is,