TL;DR: A reference in Rust is a pointer that borrows a value without taking ownership.
&Tis a shared (immutable) reference, many can exist at once.&mut Tis a mutable reference, only one can exist at a time, and no shared references can coexist with it. These rules are enforced at compile time by the borrow checker. References must not outlive the value they point to, this is enforced by lifetimes. References are Rust's primary mechanism for passing values to functions without copying or moving them.
What Is a Reference?
A reference is a pointer to a value that does not take ownership, the original owner keeps the value, and the reference is valid only for a limited scope.
fn main() {
let s = String::from("hello");
let r = &s; // borrow s; s is still the owner
println!("{r}"); // use the reference
println!("{s}"); // s is still valid; we only borrowed it
// Without references; ownership moves:
// let r = s; // s moved into r; s is no longer valid
}What Is the Difference Between &T and &mut T?
&T allows reading, &mut T allows both reading and writing. The borrow checker enforces that they never coexist.
fn main() {
let mut value = 42;
// Shared reference; can have many at once
let r1 = &value;
let r2 = &value;
println!("{r1} {r2}"); // ok; two shared references
// Mutable reference; only one, and no shared refs at the same time
let r3 = &mut value;
*r3 += 1; // dereference to modify
println!("{r3}"); // 43
// This would fail; can't have r1 and r3 active simultaneously:
// println!("{r1} {r3}"); // ❌ borrow checker error
}The rules:
- Any number of
&Tat the same time ✅ - Exactly one
&mut T, and no&Tat the same time ✅ &Tand&mut Ttogether ❌
How Do References Work in Function Parameters?
Pass &T to let a function read a value, &mut T to let it modify it, without giving up ownership.
// Takes a shared reference; caller keeps ownership
fn print_length(s: &String) {
println!("length: {}", s.len());
}
// Takes a mutable reference; can modify the caller's value
fn make_uppercase(s: &mut String) {
s.make_ascii_uppercase();
}
fn main() {
let mut text = String::from("hello");
print_length(&text); // borrow: &String
make_uppercase(&mut text); // mutable borrow: &mut String
println!("{text}"); // "HELLO"; modified in place
}What Is Automatic Dereferencing?
Rust automatically inserts * dereferences when calling methods, you rarely need to dereference manually.
fn main() {
let s = String::from("hello world");
let r = &s;
// Both work; Rust auto-derefs the reference
println!("{}", r.len()); // equivalent to (*r).len()
println!("{}", r.contains("world"));
// Explicit deref when modifying:
let mut n = 42;
let r = &mut n;
*r += 1; // must deref to assign
println!("{}", r); // auto-deref for Display
}How Do References Relate to Lifetimes?
A reference must not outlive the value it points to, the borrow checker enforces this via lifetimes.
// This fails; r would outlive the value it references
fn dangling() -> &String { // ❌ missing lifetime specifier
let s = String::from("hello");
&s // s is dropped here; returning a dangling reference
}
// Fix 1: return owned value
fn owned() -> String {
String::from("hello")
}
// Fix 2: take a reference and return a reference tied to the input
fn first_word(s: &str) -> &str { // lifetime elision makes this work
&s[..s.find(' ').unwrap_or(s.len())]
}What Is Deref Coercion?
Rust automatically converts &String to &str, &Vec<T> to &[T], and &Box<T> to &T, you can pass them interchangeably.
fn greet(name: &str) {
println!("Hello, {name}!");
}
fn main() {
let owned = String::from("Alice");
greet(&owned); // &String → &str (deref coercion)
let boxed = Box::new("Bob");
greet(&boxed); // &Box<&str> → &&str → &str
let v = vec!['a', 'b'];
let slice: &[char] = &v; // &Vec<char> → &[char]
}Frequently Asked Questions
A raw pointer (*const T, *mut T) has no safety guarantees, it can be null, dangling, or aliased. A reference (&T, &mut T) is guaranteed by the compiler to be non-null, valid, and correctly aliased. References are safe; raw pointers require unsafe.
Two &mut T to the same data would allow data races, two simultaneous writers. Rust prevents this at compile time. The single &mut T rule guarantees that mutations are always exclusive.
Moving transfers ownership, the original variable becomes invalid. Borrowing (& / &mut) creates a reference, the original variable remains valid after the borrow ends. Pass by reference when you don't need to own the value.
NLL (stable since Rust 2018) means the borrow ends at the last use, not at the end of the block. This allows code that was previously rejected to compile:
let mut v = vec![1, 2, 3];
let first = &v[0];
println!("{first}"); // last use of `first`
v.push(4); // now ok; borrow ended aboveSources
Related Glossary Terms
- Ownership: References are Rust's borrowing mechanism
- Lifetime: Lifetimes govern how long references are valid
- Borrow Checker: Enforces reference rules at compile time
- Slice: Slices are always used via references (
&[T])
Keep Reading
- Rust Ownership and Borrowing Explained: references are the primary way to borrow in Rust
- Rust Lifetimes Deep Dive: every reference has a lifetime
- Rust Memory Safety: NSA and CISA: safe references without null or dangling pointers
