Learn Computing from the Experts | The Rheinwerk Computing Blog

What Is Borrowing in Rust Programming?

Written by Rheinwerk Computing | Apr 16, 2025 1:00:00 PM

A fundamental concept in Rust’s ownership system, borrowing allows multiple parts of a program to interact with data safely and efficiently.

 

In simple terms, borrowing involves creating a reference to a value. A reference is similar to a pointer but comes with specific rules and limitations. Unlike ownership, references do not take ownership of the values they point to, which is why the process is called borrowing; a reference temporarily borrows a value without claiming ownership.

 

Borrowing involves many details and explicit rules that you must follow. In the following

sections, we’ll explore several aspects of borrowing, starting with its rationale.

 

 

Why Borrowing?

First, let’s discuss why borrowing is necessary. One key reason is the efficient use of memory, which leads to performance improvements. For instance, if a function only needs to read data, providing a reference to the data is more efficient than passing a clone or transferring ownership. Cloning or transferring ownership can be expensive, especially with data types that consume a significant amount of memory. In contrast, providing a reference is much cheaper and more efficient.

 

The second reason occurs when ownership is not required. Consider the function takes_ownership. This function simply prints the value of the vector that is passed to it. In this scenario, you probably don’t want the function to take ownership of the vector, as that ownership would dictate when the vector is freed from memory. Instead, a better approach is to temporarily borrow the vector without taking ownership of it.

 

Borrowing Rules

Now, that we understand the motivation behind borrowing, let’s go over the two rules for borrowing rules in Rust:

  • At any time, you can have either one mutable reference or many immutable references.
  • References must always be valid.

These rules solve two problems: data races and dangling references. Let’s first understand the rules, and then we’ll explain how enforcing these rules solves these two problems.

Either One Mutable Reference or Many Immutable References

Let’s start with the first rule in this section. Consider the code shown here:

 

fn main() {

   let mut vec_1 = vec![4, 5, 6];

   let ref1 = &mut vec_1;

   let ref2 = &mut vec_1; // Error

   println!("ref1: {:?}, ref2: {:?}", ref1, ref2);

}

 

We first created mutable vector of vec_1. Next, we created a mutable reference of ref1 to the vector. The ampersand (&) is used to create a reference. References can be either immutable or mutable. The mut keyword after the & indicates that a reference is mutable, allowing you to modify the borrowed data. An immutable reference, on the other hand, allows you to borrow the data without making any changes. Both types of references do not take ownership of the data; instead, they temporarily borrow it. In the next line, we created another mutable reference to the data using ref2. Finally, we access the two references in a print statement.

 

The compiler does not like it, giving us an error, which states “cannot borrow vec_1 as mutable more than once at a time.” This issue arises because we are violating the first rule: You can have either one mutable reference or many immutable references at any given time, but not both simultaneously. Two mutable references to the same data at the same time are not allowed. This rule ensures that mutation occurs in a controlled manner, preventing multiple mutable references to the same data. Many beginners find this challenging because, in most other languages, mutation is generally unrestricted.

 

If we remove the print statement, the code will compile, as shown below.

 

fn main() {

   let mut vec_1 = vec![4, 5, 6];

   let ref1 = &mut vec_1;

   let ref2 = &mut vec_1;

}

 

The issue with the code shown in earlier arises because the Rust compiler tracks the active period of a reference (also known as its scope) from the line where it is introduced or defined until the last line where the reference is used. As shown in the updated code, the scope of ref1 is limited to one line (that is, the line in which it is defined), and the scope of ref2 is limited to another line (again the line in which it is defined), with no overlap of code lines between the references. Thus, at any given time, only one mutable reference exists, so rule 1 is not violated. However, if we consider the code shown above with the print statement, printing ref1, an error will occur because, within the scope of ref1, no other mutable references to the same data should exist, which is violated by ref2.

 

According to rule 1, you can only have many immutable references to the same data. Consider the code shown here:

 

fn main() {

   let mut vec_1 = vec![4, 5, 6];

   let ref1 = &vec_1;

   let ref2 = &vec_1;

   println!("ref1: {:?}, ref2: {:?}", ref1, ref2);

}

 

Immutable references are indicated by ampersand (&), and they cannot mutate the data. It compiles since multiple immutable references are allowed by rule 1.

 

Let’s consider one more case with regard to rule 1, as shown below:

 

fn main() {

   let mut vec_1 = vec![4, 5, 6];

   let ref1 = &vec_1;

   let ref2 = &vec_1;

   let ref3 = &mut vec_1; // Error

   println!("ref1: {:?}, ref2: {:?}, ref3: {:?}", ref1, ref2, ref3);

}

 

The compiler throws an error: “cannot borrow vec_1 as mutable because it is also borrowed as immutable.” This error occurs because we are violating the rule that permits either one mutable reference or multiple immutable references at any given time, but not both simultaneously.

 

The code can be slightly modified to fix the issue, as shown here:

 

fn main() {

   let mut vec_1 = vec![4, 5, 6];

   let ref1 = &vec_1;

   let ref2 = &vec_1;

   println!("ref1: {:?}, ref2: {:?}", ref1, ref2);

   let ref3 = &mut vec_1;

}

 

The code now compiles because the scopes of ref1 and ref2 end on the print line (their last usage), and therefore, immutable and mutable references do not coexist at any given time.

 

By enforcing this rule, Rust prevents data races at compile time. A data race occurs when multiple references to the same data exist, with at least one reference modifying the data, and there’s no mechanism to synchronize access. Rust’s borrowing rules ensure that data can be either read through immutable references or modified through a single mutable reference. This approach prevents data races and allows different parts of your code to interact with data safely.

References Must Always Be Valid

Now, let’s look at the second rule, which states that references must always be valid. To illustrate this rule, let’s explore what happens when we attempt to return a reference to a value created within an inner scope, as shown:

 

fn main() {

   let vec_1 = {

      let vec_2 = vec![1, 2, 3];

      &vec_2

   };

}

 

In this example, we’ve defined a vector, vec_2, inside an inner scope. We then attempt to return a reference to vec_2 from that scope and store it in a variable, vec_1. However, when we try to compile this code, the Rust compiler raises an error “vec_2 does not live long enough.”

 

This error occurs because we’ve created what’s known as a dangling reference. Inside the inner scope, vec_2 is created and takes ownership of the vector [1, 2, 3]. We then return a reference to vec_2, intending to use it outside the scope in which it was defined. However, when the inner scope ends, vec_2 is dropped, and the memory allocated for it is cleaned up. As a result, the reference to vec_2 that we tried to return is no longer pointing to valid data; it’s now a dangling reference, which Rust’s ownership rules strictly prohibit.

 

Rust’s compiler prevents this situation by enforcing the rule that references must always be valid. This rule ensures that you can never accidentally reference memory that has already been freed, thereby preventing a class of bugs that can lead to undefined behavior in other programming languages.

 

Copying of References

Regardless of whether the data is stack allocated or heap allocated, a mutable reference to data can be copied only once. Consider the code shown below.

 

fn main() {

   let mut vec_1 = vec![4, 5, 6];

   let ref1 = &mut vec_1;

   let ref2 = ref1;

   let ref3 = ref1; // Error

}

 

The compiler does not allow the creation of multiple copies of a mutable reference and throws an error, “use of moved value: ref1.” The compiler further explains that “the move occurs because ref1 does not implement the Copy trait.” This message simply means that mutable references cannot be copied; they can only be moved.

 

Note that references typically reside on the stack. Stack-allocated data is copied, not moved, when assigned. However, mutable references, even though they are stored on the stack, are an exception. They are moved and not copied.

 

This behavior is enforced to maintain Rust’s borrowing rules. According to the first borrowing rule, you cannot have multiple mutable references to the same data at the same time. If mutable references were allowed to be copied, instead of being moved, Rust’s borrowing rules, which only permit a single mutable reference at any given time, would be violated.

 

Immutable references are, however, allowed to be copied many times, as shown here.

 

fn main() {

   let mut vec_1 = vec![4, 5, 6];

   let ref1 = &vec_1;

   let ref2 = ref1;

   let ref3 = ref1;

   let ref4 = ref1;

}

 

In this case, each assignment of ref1 creates a copy. Creating copies of immutable references does not violate any borrowing rules because the borrowing rules allow multiple immutable references. In summary, mutable references are moved when assigned, while immutable references are copied.

 

Editor’s note: This post has been adapted from a section of the book Rust: The Practical Guide by Nouman Azam. Dr. Azam is an associate professor of computer science at the National University of Computer and Emerging Sciences. He also teaches online programming courses about Rust and MATLAB, and reaches a community of more than 50,000 students. 

 

This post was originally published 4/2025.