Activity 16 - Borrowing and References Debugging
Fix the borrowing bugs in each of the following code snippets. Try pasting them in the Rust playground and work on them until they compile and run.
When you have working code, paste the corrected code, with a comment on the line you fixed saying why your fix works.
Warm-up Problems
Problem 1: Use After Move
fn main() { let data = vec![1, 2, 3]; print_data(data); println!("{:?}", data); // Fix this! } fn print_data(v: Vec<i32>) { println!("{:?}", v); }
Hint: The function takes ownership. How can you let it borrow instead?
Problem 2: Reference Confusion
fn main() { let scores = vec![85, 92, 78]; let first = scores[0]; // This works, but... let names = vec![String::from("Alice")]; let first_name = names[0]; // This doesn't! Fix it println!("First score: {}", first); println!("First name: {}", first_name); }
Hint: What's different about i32 vs String? How can you access the String without moving it?
Problem 3: Iterator Ownership
fn main() { let nums = vec![1, 2, 3]; for n in nums { println!("{}", n * 2); } println!("{:?}", nums); // Oops! Fix the loop }
Hint: How can you iterate without consuming the vector?
Problem 4: Multiple Functions Need the Same Data
fn main() { let message = String::from("Hello, Rust!"); let len = get_length(message); let upper = to_uppercase(message); println!("Length: {}, Uppercase: {}", len, upper); } fn get_length(s: String) -> usize { s.len() } fn to_uppercase(s: String) -> String { s.to_uppercase() }
Hint: Both functions try to take ownership. What if they borrowed instead?
Problem 5: Iterator Pattern Matching
fn main() { let pairs = vec![(1, 2), (3, 4), (5, 6)]; for (a, b) in pairs.iter() { let sum = a + b; // Error! Can't add references println!("{} + {} = {}", a, b, sum); } println!("Pairs still available: {:?}", pairs); }
Hint: What type does .iter() give you? The tuple pattern (a, b) doesn't automatically dereference. How can you extract the values from the references?
Challenge Problems
Problem 6: Complex Ownership Chain
fn main() { let data = vec![10, 20, 30, 40, 50]; let result = process(data); println!("Original: {:?}", data); // Want to keep using data! println!("Result: {:?}", result); } fn process(nums: Vec<i32>) -> Vec<i32> { let popped = pop_last(nums); push_7(popped) } fn pop_last(nums: Vec<i32>) -> Vec<i32> { nums.pop(); nums } fn push_7(mut nums: Vec<i32>) -> Vec<i32> { nums.push(7); nums }
Hint: Where can you borrow or clone instead of passing ownership?
Problem 7: Function Returns and Borrowing
fn main() { let data = vec![5, 10, 15, 20]; let largest = find_largest(&data); data.push(25); // Error! Fix this println!("Largest was: {}", largest); println!("Updated data: {:?}", data); } fn find_largest(numbers: &Vec<i32>) -> &i32 { let mut largest = &numbers[0]; for num in numbers.iter() { if num > largest { largest = num; } } largest }
Hint: The function returns a reference into the vector. How long does that borrow last? Would it be safe to modify the vector while that reference exists? (We'll learn the precise rules for this next time.)