Borrowing is a fundamental concept of Rust programming language. Typically, when we pass ownership of an object to a function by reference, we cannot make changes to the object. It is immutable. However, many times, we may need to modify the object within the function. In this post, we look at how to perform a Rust Borrow using Mutable References.

If this is the first time you are hearing about the concept of borrowing in Rust, I will highly recommend you go through our extremely detailed post on Ownership and Borrowing in Rust.

1 – The Problem with Borrowing References

When a function in Rust borrows a reference, it cannot modify the underlying value that the reference is pointing at.

See below example:

fn main() {
    let greeting_msg = String::from("Greetings from the future");

    modify(&greeting_msg);

}

fn modify(input_string: &String) {
    input_string.push_str(", Sarah Connor")
}

When we run the above program, we get an error.

cannot borrow `*input_string` as mutable, as it is behind a `&` reference
 --> src/main.rs:9:5
  |
8 | fn modify(input_string: &String) {
  |                         ------- help: consider changing this to be a mutable reference: `&mut String`
9 |     input_string.push_str(", Sarah Connor")
  |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `input_stri

Basically, the error says that the input_string reference is immutable. Hence, we cannot change the value of input_string in the modify() function.

This also makes sense. In real life also, when you borrow something from a friend, you cannot modify it. Your friend is the owner and when you are done using the item, you have to return it. The same is applicable for Rust as well.

2 – Borrowing a Mutable Reference in Rust

Though immutable references are the default approach, Rust allows us to modify a borrowed value using mutable references.

But how does Rust Borrow Mutable References?

Let us see an example to create a mutable reference in Rust. Check out the below snippet.

fn main() {
    let mut greeting_msg = String::from("Greetings from the future");

    modify(&mut greeting_msg);

}

fn modify(input_string: &mut String) {
    input_string.push_str(", Sarah Connor")
}

Below are the modifications we did this time:

  • We change greeting_msg to mutable using the mut keyword.
  • Next, we create a mutable reference to greeting_msg when we call the modify() function.
  • Lastly, we update the signature of modify() function to accept a mutable reference. The change in signature makes it clear that the modify() function can change the value.

This time, our program runs fine.

3 – Rust Borrow Mutable Twice or Multiple Times

While borrowing mutable references works fine, it has some limitations.

We can only have one mutable reference to a particular piece of data at any given point of time. See below example:

fn main() {
    let mut greeting_msg = String::from("Greetings from the future");

    let greeting_ref1 = &mut greeting_msg;
    let greeting_ref2 = &mut greeting_msg;

    println!("{} {}", greeting_ref1, greeting_ref2);

}

This will throw an error.

error[E0499]: cannot borrow `greeting_msg` as mutable more than once at a time
 --> src/main.rs:5:25
  |
4 |     let greeting_ref1 = &mut greeting_msg;
  |                         ----------------- first mutable borrow occurs here
5 |     let greeting_ref2 = &mut greeting_msg;
  |                         ^^^^^^^^^^^^^^^^^ second mutable borrow occurs here
6 | 
7 |     println!("{} {}", greeting_ref1, greeting_ref2);
  |                       ------------- first borrow later used here

As you can see, the error message clearly states that we cannot borrow greeting_msg more than once.

This features allows mutation in a controlled manner. After all, it is not possible for your friend to lend the same thing to two people and also allow them to transform the item at the same time.

On a programmatic level, this approach prevents data race situation at compile time itself. Data race occurs when two or more pointers access the same data at the same time and at least, one of them can modify the data. Data races can lead to unpredictable behaviour during runtime. Therefore, the Rust compiler prevents it from happening.

4 – Rust Borrow with Scoping

In the previous example, an important point to understand is scoping.

Rust prevents borrowing multiple times within the same scope. However, borrowing multiple times but in different scopes still works.

See below example:

fn main() {
    let mut greeting_msg = String::from("Greetings from the future");

    {
        let greeting_ref1 = &mut greeting_msg;
        println!("{}", greeting_ref1);
    }
  
    let greeting_ref2 = &mut greeting_msg;
    println!("{}", greeting_ref2);
}

In this case, the curly braces create a new scope. The greeting_ref1 exists in that scope. It becomes out of scope after the first println! statement. Only then, the greeting_ref2 comes into the scope.

In other words, scope plays a key role when deciding whether a mutable reference is allowed or not.

5 – Borrowing Immutable and Mutable Reference Together

We can also combine immutable and mutable references together in Rust. However, this is also governed by a few rules.

Rule # 1 – Immutable and Mutable Reference is not possible in same scope

See below example:

fn main() {
    let mut greeting_msg = String::from("Greetings from the future");

    let greeting_ref1 = &greeting_msg;
  
    let greeting_ref2 = &mut greeting_msg;
    
    println!("{} {}", greeting_ref1, greeting_ref2);
}

This will throw an error.

error[E0502]: cannot borrow `greeting_msg` as mutable because it is also borrowed as immutable
 --> src/main.rs:6:25
  |
4 |     let greeting_ref1 = &greeting_msg;
  |                         ------------- immutable borrow occurs here
5 |   
6 |     let greeting_ref2 = &mut greeting_msg;
  |                         ^^^^^^^^^^^^^^^^^ mutable borrow occurs here
7 |     
8 |     println!("{} {}", greeting_ref1, greeting_ref2);
  |                       ------------- immutable borrow later used here

Both greeting_ref1 and greeting_ref2 are in the same scope till the println! statement. Therefore, the Rust compiler does not allow us to create a mutable reference to greeting_msg in line 6.

This makes sense on a fundamental level also. Basically, you can’t just pull the rug out from under the users making use of the immutable reference and expecting the value to not change suddenly.

Rule # 2 – Multiple Immutable References are allowed in same scope.

This is opposite to the previous case. See below:

fn main() {
    let mut greeting_msg = String::from("Greetings from the future");

    let greeting_ref1 = &greeting_msg;
  
    let greeting_ref2 = &greeting_msg;
    
    println!("{} {}", greeting_ref1, greeting_ref2);
}

Here, we have two immutable references to greeting_msg. Since neither reference can change the value, this approach does not lead to any errors.

Rule # 3 – Immutable and Mutable References are allowed in different scopes

We already saw this in the previous section. But there is a slightly different flavour as well.

See below:

fn main() {
    let mut greeting_msg = String::from("Greetings from the future");

    let greeting_ref1 = &greeting_msg;
    println!("{}", greeting_ref1);
  
    let greeting_ref2 = &mut greeting_msg;
    
    println!("{}", greeting_ref2);
}

Basically, the scope ends at the point after which the reference will never be used. In this case, the scope of greeting_ref1 ends on line 5. After this, we can declare greeting_ref2 as a mutable reference without any issues.

Conclusion

Rust Borrow for Mutable References is certainly possible. However, it is important for developers to use it judiciously.

From the language point of view, Rust has made sure we don’t run into race conditions because of mutable references. Also, borrowing in Rust respects the rules associated with scoping.

Want to learn more about Rust references? Check out this post on Rust Slice Type.

If you have any comments or queries about this post, please feel free to mention them in the comments section below.

Categories: BlogRust

Saurabh Dashora

Saurabh is a Software Architect with over 12 years of experience. He has worked on large-scale distributed systems across various domains and organizations. He is also a passionate Technical Writer and loves sharing knowledge in the community.

0 Comments

Leave a Reply

Your email address will not be published. Required fields are marked *