Why this matters
The last module ended on a problem: if a function needs to look at a String, passing it moves it, and the caller loses it. Returning it every time is clumsy, cloning is wasteful. What we want is to let a function use a value without owning it. That is a reference, and creating one is called borrowing. References are everywhere in Rust code: nearly every function that reads data takes a reference. This lesson covers shared (read-only) references; the next adds mutable ones.
Creating and using a reference
fn length(s: &String) -> usize {
s.len()
}
fn main() {
let s1 = String::from("borrowing");
let n = length(&s1);
println!("{s1:?} has {n} bytes");
}
"borrowing" has 9 bytes
&s1creates a reference tos1: a value that refers tos1without owning it.- The parameter type
&Stringmeans "a reference to a String". s1is still usable after the call, because it was never moved.lengthonly borrowed it.- When
s(the reference) goes out of scope at the end oflength, nothing is dropped: a reference owns nothing.
What a reference is in memory
A reference is a pointer: the memory address of the value it refers to. It is 8 bytes on a 64-bit machine.
length's frame main's frame heap
+-----------+ +----------------------+
| s: addr --------------------> | s1: ptr --------------------> [b|o|r|r|o|w|i|n|g]
+-----------+ | len 9 |
| cap 9 |
+----------------------+
s points at s1, the String handle in main's frame, which in turn points at the heap buffer. The String is never copied and ownership never changes hands. Passing a reference costs the same as passing an integer.
fn main() {
let x = 42;
let r = &x;
println!("x = {x}, r = {r}, *r = {}", *r);
println!("r points at {:p}", r);
println!("a reference is {} bytes", std::mem::size_of::<&i32>());
}
x = 42, r = 42, *r = 42
r points at 0x7ffcee654f14
a reference is 8 bytes
Dereferencing
To get at the value a reference points to, use the dereference operator *:
fn main() {
let a = 10;
let b = &a;
let c = *b + 1;
println!("{c}");
println!("{}", b == &10);
println!("{}", *b == 10);
}
11
true
true
In practice you write * less often than you might expect, because Rust dereferences automatically in several places:
- Method calls:
s.len()works whethersis aStringor a&String(or&&String). The.operator follows references as needed ("auto-deref"). - Printing:
println!("{r}")prints the value, not the address. - Comparisons between two references compare the values they point to.
Arithmetic and comparisons between a reference and a plain value do not auto-deref: *b + 1 needs the star; b + 1 happens to work for numbers because the standard library defines + for &i32 too, but do not rely on that in general.
Borrowing rules, part 1
While a value is borrowed, the owner must respect the borrow. In particular, a value cannot be moved while a reference to it is still in use:
fn main() {
let s = String::from("hello");
let r = &s;
let t = s;
println!("{r}");
}
error[E0505]: cannot move out of `s` because it is borrowed
--> main.rs:4:13
|
2 | let s = String::from("hello");
| - binding `s` declared here
3 | let r = &s;
| -- borrow of `s` occurs here
4 | let t = s;
| ^ move out of `s` occurs here
5 | println!("{r}");
| --- borrow later used here
|
help: consider cloning the value if the performance cost is acceptable
|
3 | let r = &s.clone();
| ++++++++
E0505. If the move were allowed, t could later free the buffer while r still pointed at s. The compiler reasons about how long each reference is used (here, until the println!), and refuses any move, drop or modification of the owner in that window. The next two lessons explore these rules in depth. For now: the owner may not invalidate what a reference is looking at, while the reference is used.
Shared references can be freely duplicated; any number may exist at once:
fn main() {
let s = String::from("shared");
let r1 = &s;
let r2 = &s;
let r3 = r1;
println!("{r1} {r2} {r3} {s}");
}
shared shared shared shared
&T is Copy: copying a pointer that only reads is harmless.
&str is the better parameter type
You have seen &str since the first module. It is a reference to text: a pointer plus a length (module 6 explains its layout). For a function that only reads text, &str is more flexible than &String:
fn shout(s: &str) -> String {
s.to_uppercase()
}
fn main() {
let owned = String::from("owned string");
println!("{}", shout(&owned));
println!("{}", shout("a literal"));
}
OWNED STRING
A LITERAL
A string literal is already a &str, and a &String is automatically converted to a &str where one is expected (a deref coercion). With a &String parameter, the literal call would not compile. The same idea applies to lists: prefer &[T] (a slice, lesson 5) over &Vec<T>.
So the parameter table from the last module grows:
| parameter | caller keeps the value? | function may modify? |
|---|---|---|
s: String | no, it is moved | yes (if mut s) |
s: &str / s: &String | yes | no |
s: &mut String | yes | yes (next lesson) |
Iterating by reference
Loops over references are the same idea. for x in &array borrows the array and gives a reference to each element, leaving the array usable afterwards:
fn main() {
let words = [String::from("alpha"), String::from("beta")];
for w in &words {
println!("{w} ({} bytes)", w.len());
}
println!("still have {} words", words.len());
}
alpha (5 bytes)
beta (4 bytes)
still have 2 words
Without the &, for w in words would move each String out of the array, and the array would be unusable afterwards.
Common mistakes
Forgetting the & at the call site: length(s1) where &String is expected gives E0308 "expected &String, found String" with the help "consider borrowing here: &s1".
Using * where it is not needed is harmless but noisy; using it on a non-reference is an error (E0614 "type i32 cannot be dereferenced").
Moving the owner while a reference is alive (E0505): use the reference first, or move later.
Thinking a reference keeps a value alive. It does not. A reference must never outlive its owner, and the compiler enforces that (lesson 4). There is no reference counting behind &.
Recap
&xborrowsx: it creates a pointer toxwithout taking ownership.&Tis a pointer-sized value; dropping it frees nothing; it isCopy.*rreads the value behindr; method calls and printing dereference automatically.- While a reference is in use, the owner cannot move or drop the value (E0505).
- Prefer
&strover&Stringand&[T]over&Vec<T>for read-only parameters. for x in &collectioniterates by reference and keeps the collection.
Going further: print {:p} of a String's handle (&s) and of its buffer (s.as_ptr()) and compare them: one is a stack address, the other a heap address. The Rust Book section 4.2 covers references with the same diagrams.