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:
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,
- 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