Recommended resources
The freely available resources this lesson will direct you to.
Hello! Last time, you saw that passing a String by value moves it into a function. If the function only needs to inspect the text, transferring ownership—or returning the string just to keep using it—is unnecessary. This lesson introduces immutable references: a way to give code access to a value while its original owner keeps it.
You will use references in function calls, see why several readers can borrow the same value, and learn the basic constraint that makes borrowed access safe. This is a useful pattern for functions that inspect request data without needing to own it.
Let a function inspect a value
Suppose a function needs the length of a status message. A by-value parameter would take ownership of its String:
fn status_length(status: String) -> usize {
status.len()
}
If the caller passed a String to this function, it could not use that string afterward. Instead, put & before the argument and before the parameter type:
fn status_length(status: &String) -> usize {
status.len()
}
fn main() {
let status = String::from("queued");
let length = status_length(&status);
println!("{status} has {length} bytes");
}
At the call, &status creates a reference to the String. The type &String means “a reference to a String,” not “an owned String.” The function receives access to the value, but status remains its owner. When status_length returns, its parameter stops being usable; it does not drop the caller’s String.
Watch how the change to both sides of the function call resolves an initial type mismatch:
References and Borrowing in Rust - Full Crash Rust Tutorial for Beginners
Watch “References and Borrowing in Rust” by Francesco Ciulla for a short demonstration of passing a reference rather than moving a String.
In borrowing a String, notice that adding & only at the call first produces a type error. The parameter must also change from String to &String; afterward, the caller can still use its original value.
The Rust book gives a more precise account of what remains owned and what is merely borrowed:
References and Borrowing - The Rust Programming ...
Read the opening of “References and Borrowing” in the official Rust book to connect the function-call syntax to ownership.
In “References and Borrowing,” read the opening explanation, starting with the problem of returning a String and ending with why a borrowed parameter needs no ownership returned. Then read the borrowing definition immediately below. Focus on the distinction between the reference going out of scope and the referenced String being dropped.
Here, status.len() works even though status is a reference. Rust lets you call the String method through that reference without manually following it. One detail worth keeping accurate: a String’s len() counts bytes, not necessarily human-perceived characters. The ASCII message "queued" happens to have six of each.
One owner, several readers
Borrowing does not create a second owner. You can make multiple immutable references to the same value because none of them can change it:
fn main() {
let status = String::from("queued");
let first: &String = &status;
let second: &String = &status;
println!("First view: {first}");
println!("Second view: {second}");
println!("Owner's view: {status}");
}
All three lines can read the same text. Only status owns the String, so only its ownership determines when that string is dropped. An immutable reference is also called a shared reference: sharing read access does not share ownership.
Read-only access is a property of the reference, even if the owner was declared mutable:
fn main() {
let mut status = String::from("queued");
let view = &status;
println!("{view}");
status.push_str(" for processing"); // Allowed after the last use of view.
println!("{status}");
}
view cannot be used to call a method that changes the string. And while that shared borrow is still needed, the owner cannot change the borrowed value behind the reader’s back. Rust can see that view is last used at the first println!, so the later change is allowed.
For contrast, this version does not compile:
fn main() {
let mut status = String::from("queued");
let view = &status;
// status.push_str(" for processing"); // Not allowed here.
println!("{view}"); // The shared borrow is still needed here.
}
Uncommenting push_str asks Rust to change status before the final use of view. The important rule for now is: an immutable borrow gives read access, not permission to change the borrowed value. The next lesson will introduce mutable references for functions that do need to change something.
A reference cannot outlive its owner
A reference is useful only while the value it refers to still exists. Rust checks this rather than leaving you with a reference to memory that has been freed:
fn main() {
let view;
{
let status = String::from("queued");
view = &status;
} // status is dropped here.
// println!("{view}"); // Error if uncommented: its owner is gone.
}
The inner status cannot be dropped while an active reference to it is later used. In this example, uncommenting the final line makes the program fail to compile. This is a practical difference between a safe Rust reference and an unchecked pointer: a reference must point to a valid value whenever you use it.
For functions that only need to inspect an input, you can now make a deliberate choice. A parameter of type String receives ownership; a parameter of type &String temporarily borrows access. For example, a function that displays a request status need not consume it:
fn display_status(status: &String) {
println!("Current status: {status}");
}
fn main() {
let status = String::from("queued");
display_status(&status);
println!("Still available: {status}");
}
We are using &String here to make the ownership distinction clear. A later lesson on string slices will introduce &str, which is often a more flexible parameter type for read-only text.
Conclusion
Use &value to create an immutable reference and &Type to accept one in a function. The function can inspect the value without becoming its owner, so the caller can use its owned value afterward. Multiple immutable references may read the same value, but they cannot change it, and Rust ensures a reference is not used after its owner is gone.
Next, you will use mutable references when a function needs to modify a value without taking ownership of it.