Skip to content

References & Borrowing

Moving a value into a function is often unnecessary — you just want the function to read the value, not own it. Rust solves this with references. A reference lets you pass a value to a function without giving up ownership.

The syntax &T means “a shared reference to a value of type T”. Creating a reference is called borrowing.

fn calculate_length(s: &String) -> usize {
s.len()
} // s goes out of scope, but since it does not own the data, nothing is dropped
fn main() {
let s = String::from("hello");
let len = calculate_length(&s); // pass a reference, not the value
println!("{} has {} characters", s, len); // s is still valid
}

The &s syntax creates a reference to s. The function signature s: &String declares that the parameter is a reference, not an owned String.

Because a reference does not own the value it points to, the value is not dropped when the reference goes out of scope. Ownership stays with the original variable.

fn print_greeting(name: &String) {
println!("Hello, {}!", name);
} // reference dropped here — name itself is unaffected
fn main() {
let name = String::from("Alice");
print_greeting(&name);
print_greeting(&name); // can borrow again — we still own name
println!("Still have: {}", name);
}

You can have any number of shared (&T) references to a value at the same time, as long as none of them are mutable references. Shared references are read-only.

fn main() {
let s = String::from("shared");
let r1 = &s;
let r2 = &s;
let r3 = &s;
println!("{} {} {}", r1, r2, r3); // all three are valid simultaneously
}

When a function parameter is &String, the caller passes &my_string. When the parameter is &str (a string slice), the function is more flexible — it accepts both &String and string literals. Prefer &str over &String for function parameters as a general rule.

fn greet(name: &str) {
println!("Hello, {}!", name);
}
fn main() {
let owned = String::from("Bob");
greet(&owned); // &String coerces to &str
greet("Charlie"); // string literal is already &str
}
fn calculate_length(s: &String) -> usize {
s.len()
}
fn greet(name: &str) {
println!("Hello, {}!", name);
}
fn main() {
let s = String::from("hello");
// Pass a reference — s is not moved
let len = calculate_length(&s);
println!("length of {} is {}", s, len);
// Multiple shared borrows are allowed
let r1 = &s;
let r2 = &s;
println!("r1={} r2={}", r1, r2);
// &String coerces to &str
let owned = String::from("Alice");
greet(&owned); // borrow, not move
greet("Bob"); // string literal
println!("owned is still: {}", owned);
}
What does the & symbol do when placed before a type in a function parameter?
What happens to a String when a function that takes &String returns?
How many shared (&T) references to the same value can exist simultaneously?
Why should function parameters prefer &str over &String?