Borrow Data with References

RustBeginner
Practice Now

Introduction

Moving a String transfers ownership, but many functions only need temporary access. Rust calls that temporary access a borrow and represents it with a reference.

You will complete a prepared guest-list program in three small stages: inspect owned text through a shared reference, intentionally observe why shared access cannot modify it, and then make one controlled update through a mutable reference. The final output will prove that main still owns and can use the list.

Inspect Data Through a Shared Reference

In this step, you will lend a function read-only access to a String instead of moving ownership into it.

Enter the prepared Cargo project and open its source:

cd /home/labex/project/guest-borrowing
nano src/main.rs

The setup created the complete project and left one placeholder in the call to inspect_guests. Its parameter type is &String. The & means “a reference to a String”: the function may temporarily view the value, but it does not become the owner.

Replace only /* Step 1: borrow guest_list */ with &guest_list, so the call becomes:

    inspect_guests(&guest_list);

Here, &guest_list creates a shared reference. Shared means read-only access may be shared without transferring the String itself.

Save with Ctrl+O, press Enter, and exit with Ctrl+X. Run the program:

cargo run --quiet

The output should be:

Inspecting: Ari, Bo
Caller still owns: Ari, Bo
Before update: Ari, Bo
Updated: Ari, Bo

The first line comes from the borrowed reference. The second line is the important ownership evidence: after inspect_guests returns, main can still use guest_list. The update function also reads the current value, but it does not change it yet.

Observe the Shared-Reference Limit

In this step, you will intentionally try to modify the String through a shared reference. The failed compiler check is expected and gives you evidence for Rust's borrowing rule.

Open the source again:

nano src/main.rs

Inside add_guest, remove only the leading // from the prepared line so it becomes:

    list.push_str(", Mina");

The push_str method appends text to a String, so it requires permission to modify the value. However, list currently has type &String, which grants shared, read-only access.

Save and exit. Before checking, predict the result: the compiler should reject the attempted modification because a shared reference cannot provide mutable access.

Run the check. Failure is expected:

cargo check

Find these stable parts of the diagnostic:

error[E0596]: cannot borrow `*list` as mutable, as it is behind a `&` reference
...
consider changing this to be a mutable reference

The *list wording refers to the String reached through the reference. Rust is not saying the String can never change; it is saying this particular &String does not carry permission to change it. You will grant controlled mutable access in the next step.

Update Through a Mutable Reference

In this step, you will give add_guest temporary, exclusive permission to modify the caller's String.

A mutable reference has the form &mut T. A useful beginner rule is many readers or one writer at a time: shared references allow reading, while one mutable reference grants temporary exclusive write access. That exclusivity prevents two parts of a program from changing the same value unexpectedly.

Open the file:

nano src/main.rs

Make three focused edits:

  • Change the add_guest parameter from &String to &mut String.
  • Add mut to the binding so it reads let mut guest_list = ....
  • Change the add_guest call from &guest_list to &mut guest_list.

The completed function signature and call should have these forms:

fn add_guest(list: &mut String) {
    add_guest(&mut guest_list);

The two uses of mut have connected but different jobs. let mut guest_list says the owned value is allowed to change. &mut guest_list then creates one temporary mutable reference that lends exclusive write access to add_guest for the duration of the call.

Save and exit, then check the repair:

cargo check

A Finished result without E0596 means the permissions now agree. Run the program:

cargo run --quiet

The final output should be:

Inspecting: Ari, Bo
Caller still owns: Ari, Bo
Before update: Ari, Bo
Updated: Ari, Bo, Mina

The mutable borrow ends when add_guest returns. The final print can read guest_list again, proving that ownership stayed in main while the function made a controlled update.

Summary

You used &String for shared, read-only access and &mut String for temporary, exclusive modification. In both cases the caller retained ownership, and the compiler helped enforce the difference between viewing and changing borrowed data.