Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Of course. This is an excellent question because it gets to the heart of Rust's ownership and borrowing system. Thinking about this without a compiler forces you to understand the underlying principles.

Here is a guide and a mental model to help you decide when to use &.

The Core Question: Ownership vs. Borrowing

The fundamental question you must always ask yourself is:

"Does this piece of code need to own the data, or does it just need to use it for a while?"

Owning (T): If the function needs to take the data and become its new owner, you pass the value directly (e.g., my_string). The original owner can no longer use it.

Borrowing (&T or &mut T): If the function just needs to read or modify the data temporarily and then give it back, you pass a reference (e.g., &my_string). The original owner retains ownership and can use the data again after the function call.

The Library Book Analogy (A Mental Model)

Imagine your data is a book.

Giving the book away (Passing by Value: T): You give your only copy of the book to a friend. You no longer have it. Your friend now owns it and can do whatever they want with it, including giving it to someone else.

#![allow(unused)]
fn main() {
// You have the book
let book = String::from("The Rust Programming Language");
// You give it to your friend (the function)
read_and_keep(book);
// You can't read the book anymore, this would be a compiler error!
// println!("{}", book);
}

Letting a friend read your book (Immutable Borrow: &T): You let a friend look at your book. They can read it, but they can't write in it or tear out pages. When they're done, they give it back. You can even let multiple friends read it at the same time. This is the most common and safest way to share.

#![allow(unused)]
fn main() {
// You have the book
let book = String::from("The Rust Programming Language");
// You let a friend read it
glance_at_title(&book);
// You still have the book and can read it yourself.
println!("I still have: {}", book);
}

Letting a friend edit your book (Mutable Borrow: &mut T): You let one, and only one, trusted friend borrow your book to make corrections. While they have it, nobody else (not even you!) can read or write in it. This ensures there are no conflicting edits. When they're done, they give it back with the changes.

#![allow(unused)]
fn main() {
// You have the book
let mut book = String::from("Programming in Rus");
// You let a friend make a correction
add_character(&mut book);
// You get the edited book back.
println!("The corrected title is: {}", book);
}

A Decision-Making Flowchart (For When You're Coding)

When writing a function signature, ask yourself these questions in order:

Question 1: Will this function modify the data it receives?

YES → You almost certainly need a mutable borrow: &mut T.

Example: A function that appends a suffix to a String.

#![allow(unused)]
fn main() {
// The function needs to change the string in place.
fn append_world(s: &mut String) {
    s.push_str(", world!");
}
}

NO → Go to Question 2.


Question 2: Is the data "cheap" to copy?

Rust has a special trait called Copy. Types that have this trait are simple values (like integers, booleans, floating-point numbers) that live on the stack and are trivial to duplicate.

YES, it's a Copy type (like i32, f64, bool, char, or a tuple of Copy types) → Just pass it by value: T.

It's simpler and often just as fast as a reference because copying a few bytes is instantaneous.

Example: A function that adds two numbers.

#![allow(unused)]
fn main() {
// i32 is a Copy type. It's cheap to copy.
fn add(a: i32, b: i32) -> i32 {
    a + b
}
}

NO, it's complex/large data (like String, Vec<T>, a struct you defined) → Go to Question 3.


Question 3: Does the function need to take ownership of the data?

This usually happens for one of these reasons:

  • The function will store the data in a struct or enum.
  • The function will send the data to another thread.
  • The function's whole purpose is to transform the data and return a new, owned value, consuming the original in the process.

YES, it needs to consume the data → Pass it by value: T.

Example: A Person struct that stores a name. The struct needs to own the String.

#![allow(unused)]
fn main() {
struct Person {
    name: String,
}

impl Person {
    // The Person struct must OWN the name, so we take `String` by value.
    fn new(name: String) -> Self {
        Person { name }
    }
}
}

NO, the function just needs to read the data → Use an immutable borrow: &T.

This is the most common case for complex data. You avoid a potentially expensive copy and you signal that the function will not change the data.

Example: A function that calculates the length of a String.

#![allow(unused)]
fn main() {
// The function only needs to READ the string to find its length.
// It doesn't need to own it or change it.
fn calculate_length(s: &String) -> usize {
    s.len()
}
}

Quick Reference "Cheat Sheet"


Goal Your thought process Syntax Example


Read Only "I just need to look at this data. &T (or fn print_summary(item: &Item) I won't change it." &str)

Modify in "I need to change this data &mut T fn clear_vector(vec: &mut Vec<i32>) Place directly."

Take "I need to store this data in a T fn create_user(name: String) -> User Ownership struct or send it to another (The User struct owns name) thread."

Simple Data "This is an i32 or bool. It's T fn is_positive(num: i32) -> bool (e.g., cheap to copy."
numbers)


Final Rule of Thumb

When in doubt, start with a borrow (&T). It's the most flexible option. If you later find out you need to modify it, change to &mut T. Only take ownership (T) if you have a clear and explicit reason why the function needs to consume the value.