Ownership and Borrowing
Learning Objectives
- You understand Rust’s ownership rules and why they exist.
- You can explain what happens when values are moved.
- You know how to borrow values using references.
- You understand the difference between mutable and immutable references.
- You can work with String and &str types.
Warming up
Here is a set of tiny programming tasks to get you familiar with syntax that relates to ownership and borrowing. You can solve these exercises directly in the embedded code editor.
Ownership and Rust
In many programming languages, you either manually manage memory (which is error-prone) or rely on a garbage collector (which adds runtime overhead). Rust takes a different approach: it uses a system of ownership with a set of rules that the compiler checks at compile time.
The ownership system ensures that:
- Memory is freed automatically when it’s no longer needed
- You can’t accidentally use memory after it’s been freed
- You can’t have data races in concurrent code
While this might sound complex, ownership is handled in large part by the compiler, and for a programmer it suffices to only know a couple of basic concepts and rules instead of the whole theory behind it.
The three rules of ownership are the foundation of Rust’s memory management system. They govern how values are owned, moved, and borrowed in Rust programs. They are as follows:
- Each value in Rust has a variable that’s called its owner.
- There can only be one owner at a time.
- When the owner goes out of scope, the value is dropped (freed).
The Three Rules of Ownership
0 / 3 points
Which of the following statements correctly describe Rust’s ownership rules?
Ownership in Action
Let’s start with a simple example using strings. We’ll create a String that stores the name of a coffee drink:
Here, drink is the owner of the String value “Espresso”. When drink goes out of scope at the end of the main function, the String is automatically freed.
Moving Values
Now, let’s see what happens when we assign one variable to another:
When we write let drink2 = drink1;, the ownership of the String is moved from drink1 to drink2. After this point, drink1 is no longer valid. If you uncomment the last line, you’ll get a compiler error:
error[E0382]: borrow of moved value: `drink1`This is Rust preventing you from using a value after it has been moved. The data itself hasn’t been copied - just the ownership has transferred.
Passing Values to Functions
The same thing happens when you pass a value to a function:
When we call print_drink(order), ownership of the String moves into the function. After the function returns, the String is dropped. The variable order in main is no longer valid.
This might seem restrictive, but it prevents a whole class of bugs! You can’t accidentally use a value after it’s been freed.
Ownership and Functions
0 / 3 points
What happens to the variable name in this code?
fn greet(s: String) { println!("Hello, {}", s);}
fn main() { let name = String::from("Alice"); greet(name); // Can we use name here?}Some Types are Automatically Copied Instead of Moved
Although we just saw that ownership moves by default, not all types behave this way. See, for example, this code with integers:
Why can we use cups1 after assigning it to cups2? The reason is that integers implement the Copy trait (we’ll look into traits later in this part). Types that implement Copy are automatically copied instead of moved. This is safe for simple types like integers, booleans, and floats because they have a known, fixed size and live on the stack.
Note: Types that implement Copy include:
- All integer types (i32, u64, etc.)
- All floating-point types (f32, f64)
- bool
- char
- Tuples, if they only contain types that implement Copy
String does not implement Copy because it manages data on the heap and copying it would be expensive.
Moving vs Copying Values
0 / 3 points
Consider the following code:
let x = 5;let y = x;println!("{}", x);Why does this code compile successfully?
References and Borrowing
If moving values makes them unusable, how do we write useful programs? The answer is borrowing. Instead of giving ownership of a value to a function, we can lend it using a reference.
Immutable References
A reference allows you to refer to a value without taking ownership of it. References are created with the & operator:
Now print_drink takes a reference to a String (&String) instead of a String. When we call it, we pass &order - a reference to order. The function can read the value, but it doesn’t own it. After the function returns, we can still use order in main.
This is called borrowing: the function borrows the value but gives it back when it’s done.
Multiple Immutable References
You can have as many immutable references to a value as you want:
This is safe because none of these references can modify the value - they can only read it.
Mutable References
What if you need to modify a value? For that, you need a mutable reference, created with &mut:
Note: Both the variable (
let mut order) and the reference (&mut order) must be declared as mutable.
Immutable References
0 / 3 points
Which of the following statements about immutable references are true?
Functions to modify a string
0 / 10 points
Create the following three functions:
happytakes aStringas a parameter and modifies it by appending ” :)” to it.sadtakes aStringas a parameter and modifies it by appending ” :(” to it.takes_ownershiptakes aStringas a parameter and simply prints it.
The first two functions should use mutable references to modify the string passed to them, while the last function should take ownership of the string and print it.
Function call examples:
let mut mood = String::from("I am feeling great");happy(&mut mood); // mood is now "I am feeling great :)"println!("{}", mood); // prints "I am feeling great :)"sad(&mut mood); // mood is now "I am feeling great :) :("println!("{}", mood); // prints "I am feeling great :) :("takes_ownership(mood); // prints "I am feeling great :) :("// println!("{}", mood); // When uncommented, this line should cause a compile-time errorDereferencing to Read and Modify Values
When you have a reference to a value, you sometimes need to use the dereference operator (*) to access or modify the underlying value.
The * operator “follows” the reference to get to the actual value. When you write *count_ref += 3, you’re saying “get the value that count_ref points to and add 3 to it.”
Dereferencing
0 / 3 points
What is the purpose of the * operator in this code?
let mut count = 10;let count_ref = &mut count;*count_ref += 5;You might wonder why we didn’t need to use
*in the earlieradd_sugarexample. The reason is that Rust automatically dereferences references when calling methods and we were calling a method (push_str) on the reference. For direct operations like assignment or arithmetic, however, you need to explicitly use*.
Here are both approaches side by side:
Note: You can also read through a reference without dereferencing when printing or comparing, because Rust’s formatting and comparison traits automatically dereference for you.
You can also use * to read values through immutable references, though it’s often not necessary:
Change to Fahrenheit
0 / 15 points
Create a function that takes a temperature in Celsius as a parameter and changes the value given to it to the corresponding temperature in Fahrenheit. The formula to convert Celsius to Fahrenheit is: F = C * 9/5 + 32.
The function should use a mutable reference to modify the temperature value passed to it.
Function call examples:
let mut temp = 0.0;change_to_fahrenheit(&mut temp);println!("temp is now {}", temp); // prints temp is now 32.0The Borrowing Rules
Here’s the key restriction: you can have either one mutable reference OR any number of immutable references, but not both at the same time.
This code will not compile:
fn main() { let mut order = String::from("Espresso");
let ref1 = ℴ // Immutable borrow let ref2 = ℴ // Another immutable borrow - OK let ref3 = &mut order; // Mutable borrow - ERROR!
println!("{}, {}, {}", ref1, ref2, ref3);}The compiler will complain:
error[E0502]: cannot borrow `order` as mutable because it is also borrowed as immutableWhy this restriction? It prevents data races at compile time. If you could read a value while someone else is modifying it, you might see inconsistent data.
However, this code will work:
This works because the immutable references (ref1 and ref2) are no longer in use when we create the mutable reference. Rust is smart enough to see that the scopes don’t overlap.
The Borrowing Rules
0 / 3 points
Which code snippet will compile successfully?
Note: The scope of a reference starts where it’s introduced and continues through its last use, not necessarily to the end of the block. This is called Non-Lexical Lifetimes (NLL); see also the original RFC 2094.
Reference Scope and Non-Lexical Lifetimes
0 / 3 points
Why does this code compile successfully?
let mut text = String::from("Hello");let r1 = &text;println!("{}", r1);let r2 = &mut text;r2.push_str("!");The String Type
In this chapter, we’ve been using String in our examples, but in the last chapter, we used string slices (&str). What’s the difference?
String vs &str
- String is an owned, growable string type. It manages its own memory on the heap. You can create, modify, and own
Stringvalues. - &str is a string slice - a reference to a string stored somewhere else. You can get a
&strfrom aString, but not the other way around. You cannot modify a&strbecause it’s just a view into existing data.
When to Use Each
Here’s a general rule:
- Use &str for function parameters when you just need to read the string
- Use String when you need to own or modify the string
Notice that display_menu_item takes &str - this is flexible because:
- You can pass string literals directly:
"Espresso" - You can pass String references:
&drink
This is why &str is the recommended type for function parameters when you only need to read the string.
String vs String Slice
0 / 3 points
Which statements correctly describe the difference between String and &str?
String Operations
Here are some common String operations: ,
Note: The
.to_string()method converts a&strto aString. You’ll use this when you need an owned string from a string literal.
Message Builder
0 / 15 points
Create a message builder that is composed of the following three functions:
fn create_greeting(name: &str) -> Stringreturns a new mutable owned String with “Dear [name],\n”.fn add_message(greeting: &mut String, message: &str)appends the message to the greeting with a newline at the end.fn finalize_message(mut message: String, signature: &str) -> Stringappends “\nBest regards,\n[signature]” to the message and returns the finalized message.
Function call examples:
let name = "Alice";let mut msg = create_greeting(name);add_message(&mut msg, "Hope you're doing well!");add_message(&mut msg, "Let's meet for coffee soon.");let final_msg = finalize_message(msg, "Bob");
println!("{}", final_msg);Should output:
Dear Alice,Hope you're doing well!Let's meet for coffee soon.
Best regards,BobExample: Coffee Shop Inventory
Let’s build a more complete example that demonstrates ownership and borrowing. We’ll create a simple inventory system for our coffee shop.
Let’s examine what’s happening with ownership and borrowing:
-
check_stock borrows
itemimmutably with&str. It only needs to read the name, not modify it. It also takesquantityby value (a copy) since i32 implements Copy. -
add_stock borrows
itemimmutably (&str) andquantitymutably (&mut i32). It needs to modify the quantity, so it requires a mutable reference. The*operator dereferences the mutable reference to access and modify the value. -
sell_item is similar to
add_stock- it needs to modify the quantity, so it takes a mutable reference. -
In main, we own the
Stringvalues (coffee_beansandmilk). We pass references to these strings to the functions, so they can use them without taking ownership. The stock quantities are mutable (mut), and we pass mutable references when we need to modify them.
Notice that we can still use coffee_beans and milk after passing them to functions because we only borrowed them - we didn’t move ownership.
Update Stock Function
0 / 10 points
The assignment template comes with the above coffee shop inventory program. Add a function update_stock that takes three parameters: (1) the item name as a string slice, (2) a mutable reference to the stock quantity as an integer, and (3) an integer amount to add (or subtract, if negative) from the stock. The function should update the stock quantity accordingly.
Don’t allow the stock to go below zero; if the subtraction would cause it to go negative, do not update the stock and print a warning message instead.
Function call example:
let item = "Espresso";let mut stock = 10;update_stock(item, &stock, -3); // stock is now 7update_stock(item, &stock, -10); // prints warning, stock remains 7update_stock(item, &stock, 5); // stock is now 12Common Mistakes and Solutions
Let’s look at some common mistakes students make when learning ownership:
Mistake 1: Trying to use a moved value
fn main() { let item = String::from("Coffee"); let item2 = item; // item is moved println!("{}", item); // Error!}Solution: Use a reference instead, or clone the value:
Mistake 2: Multiple mutable references
fn main() { let mut count = 5; let r1 = &mut count; let r2 = &mut count; // Error! Can't have two mutable references *r1 += 1; *r2 += 1;}Solution: Make sure mutable references don’t overlap:
Mistake 3: Mixing immutable and mutable references
fn main() { let mut text = String::from("Hello"); let r1 = &text; let r2 = &mut text; // Error! Can't borrow as mutable while immutable borrow exists println!("{} {}", r1, r2);}Solution: Make sure immutable borrows are done before mutable borrow:
Mistake 4: Forgetting to dereference mutable references
fn main() { let mut count = 5; let count_ref = &mut count; count_ref += 3; // Error! Can't add to a reference}Solution: Use the dereference operator:
Common Ownership Mistakes
0 / 3 points
How can you fix this code that tries to use a moved value?
let item = String::from("Coffee");let item2 = item;println!("{}", item); // Error!Summary
In this chapter, we explored Rust’s ownership system:
- Ownership rules: Each value has one owner, and the value is dropped when the owner goes out of scope
- Moving values: Assignment and function calls transfer ownership
- Copy trait: Simple types like integers are copied instead of moved
- Borrowing: References let you access values without taking ownership
- Immutable references (
&T): Multiple allowed, can only read - Mutable references (
&mut T): Only one allowed, can read and write - The borrowing rules: Either one mutable reference OR any number of immutable references
- String types:
Stringfor owned strings,&strfor string slices
The ownership system might feel restrictive at first, but it prevents entire classes of bugs:
- No dangling pointers
- No use-after-free errors
- No data races
- Memory is automatically managed safely