This lesson is exercise-driven. You'll combine everything from Lessons 09–13 — ownership, borrowing, structs, and enums — to solve practical problems.
Read buggy code, diagnose the cause, and fix it. Repeat until it clicks!
Level 1: Simple Move Errors
Problem 1-1
fn main() {
let name = String::from("Alice");
let greeting = name;
println!("Name: {name}");
println!("Greeting: {greeting}");
}
error[E0382]: borrow of moved value: `name`
Diagnosis: name was moved to greeting. After a move, the original variable can't be used.
Fix 1 — Use the new owner after the move:
fn main() {
let name = String::from("Alice");
let greeting = name;
println!("Greeting: {greeting}");
}
Fix 2 — Create a copy:
fn main() {
let name = String::from("Alice");
let greeting = name.clone();
println!("Name: {name}");
println!("Greeting: {greeting}");
}
Problem 1-2
Fix the code below so it compiles — without using clone().
fn print_length(s: String) -> usize {
println!("String: {s}");
s.len()
}
fn main() {
let word = String::from("Rust");
let len = print_length(word);
println!("{word}'s length: {len}");
}
Hint
Change the parameter type so the function doesn't take ownership. Use &str or &String.
Solution
fn print_length(s: &str) -> usize {
println!("String: {s}");
s.len()
}
fn main() {
let word = String::from("Rust");
let len = print_length(&word);
println!("{word}'s length: {len}");
}
Level 2: Borrowing in Function Parameters
Problem 2-1
fn add_greeting(names: &Vec<String>) {
for name in names {
names.push(format!("Hello, {name}!"));
}
}
Why doesn't this compile?
Diagnosis
names is borrowed as &Vec (immutable reference), but push() tries to modify the vector. You have read permission but are trying to edit!
On top of that, modifying a vector while iterating over it could corrupt memory. Rust catches this at compile time.
Problem 2-2
Fix the code below so it compiles.
fn longest(s1: String, s2: String) -> String {
if s1.len() >= s2.len() {
s1
} else {
s2
}
}
fn main() {
let a = String::from("hello");
let b = String::from("hi");
let result = longest(a, b);
println!("Longer string: {result}");
println!("a = {a}"); // error!
}
Hint
Use borrowing so the function doesn't take ownership. The return type also needs to become a reference.
Solution
fn longest(s1: &str, s2: &str) -> &str {
if s1.len() >= s2.len() {
s1
} else {
s2
}
}
fn main() {
let a = String::from("hello");
let b = String::from("hi");
let result = longest(&a, &b);
println!("Longer string: {result}");
println!("a = {a}"); // OK!
}
Note: This code may require lifetime annotations. That's covered in the next lesson!
Level 3: Ownership in Structs + Methods
Problem 3-1
#[derive(Debug)]
struct Playlist {
name: String,
songs: Vec<String>,
}
impl Playlist {
fn add_song(self, song: String) {
self.songs.push(song);
}
fn show(&self) {
println!("--- {} ---", self.name);
for song in &self.songs {
println!(" {song}");
}
}
}
fn main() {
let playlist = Playlist {
name: String::from("My Playlist"),
songs: vec![String::from("Song 1")],
};
playlist.add_song(String::from("Song 2"));
playlist.show();
}
There are two errors. Find and fix them!
Hint
add_songtakesselfby value — it takes ownership. Since you only need to modify, what form of self is appropriate?self.songs.push()requiresselfto be mutable.- The
playlistvariable also needs to be mutable.
Solution
impl Playlist {
fn add_song(&mut self, song: String) {
self.songs.push(song);
}
// show stays the same
}
fn main() {
let mut playlist = Playlist {
name: String::from("My Playlist"),
songs: vec![String::from("Song 1")],
};
playlist.add_song(String::from("Song 2"));
playlist.show();
}
Change self to &mut self and declare playlist as mut.
Revisiting Phase 1 Code
In Lesson 06 you wrote code like this:
fn print_scores(scores: Vec<i32>) {
for s in scores {
println!("{s}");
}
}
fn main() {
let scores = vec![90, 85, 77];
print_scores(scores.clone()); // fixed with clone()
println!("Total: {} scores", scores.len());
}
Now you can fix it without clone():
fn print_scores(scores: &[i32]) {
for s in scores {
println!("{s}");
}
}
fn main() {
let scores = vec![90, 85, 77];
print_scores(&scores); // just borrow
println!("Total: {} scores", scores.len());
}
The parameter changed to &[i32] (a slice reference). No copying needed. Now you know why clone() was necessary — and what the better alternative is.
Ownership Decision Cheat Sheet
When writing code, follow this flow:
Need to pass a value
├─ Read-only? → &T (immutable reference)
├─ Need to modify? → &mut T (mutable reference)
├─ Need ownership? → T (move)
└─ Not sure? → Start with &, adjust if you get errors
Exercise 1 (easy). Fix the code below so it compiles.
fn main() {
let mut items = vec!["apple", "banana"];
let first = &items[0];
items.push("grape");
println!("First: {first}");
}
Exercise 2 (medium). Complete the Student struct. add_score adds a score and average returns the mean.
#[derive(Debug)]
struct Student {
name: String,
scores: Vec<f64>,
}
impl Student {
fn new(name: &str) -> Student {
// Complete this
}
fn add_score(/* ??? */, score: f64) {
// Complete this
}
fn average(/* ??? */) -> f64 {
// Complete this
// Hint: return 0.0 if scores is empty
}
}
fn main() {
let mut student = Student::new("Alice");
student.add_score(90.0);
student.add_score(85.0);
student.add_score(92.0);
println!("{}: average {:.1}", student.name, student.average());
}
Exercise 3 (challenge). Use the enum and functions below to build a simple calculator.
enum Operation {
Add(f64, f64),
Subtract(f64, f64),
Multiply(f64, f64),
}
fn calculate(op: &Operation) -> f64 {
// Use match to return the result of each operation
}
fn describe(op: &Operation) -> String {
// Return a string like "3 + 5 = 8"
}
Q1. When a function only reads a value, the most appropriate parameter type is:
- A)
String - B)
&str - C)
mut String - D)
String::from()
Q2. Why does this code produce an error?
fn main() {
let mut v = vec![1, 2, 3];
let first = &v[0];
v.push(4);
println!("{first}");
}
- A) Vectors can't hold more than 3 elements
- B) An immutable reference (
first) exists while the vector is being modified - C) You can't pass a reference to
println! - D) Vectors created with
vec!can't be modified
Q3. Why is borrowing (&) better than clone()?
- A) The code is shorter
- B) It doesn't copy data, saving memory and time
- C) Errors never occur
- D) It works on every type