Borrowing rules in Rust - Time & Space Complexity
We want to understand how borrowing rules affect the speed of Rust programs.
Specifically, how does the program's work grow when borrowing is involved?
Analyze the time complexity of the following code snippet.
fn sum_elements(numbers: &Vec) -> i32 {
let mut total = 0;
for num in numbers {
total += *num;
}
total
}
fn main() {
let data = vec![1, 2, 3, 4, 5];
let result = sum_elements(&data);
println!("Sum: {}", result);
}
This code sums all numbers in a list by borrowing the list without taking ownership.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: Looping through each element in the vector.
- How many times: Once for each element in the list.
As the list gets bigger, the program does more work by visiting each number once.
| Input Size (n) | Approx. Operations |
|---|---|
| 10 | 10 additions |
| 100 | 100 additions |
| 1000 | 1000 additions |
Pattern observation: The work grows directly with the number of items.
Time Complexity: O(n)
This means the time to sum grows in a straight line with the list size.
[X] Wrong: "Borrowing makes the code run faster because it avoids copying."
[OK] Correct: Borrowing helps with memory safety and avoids copying, but the time to visit each element still depends on the list size.
Understanding how borrowing affects program speed helps you write safe and efficient Rust code, a skill valued in many coding challenges.
"What if we changed the function to take ownership of the vector instead of borrowing? How would the time complexity change?"
Practice
Solution
Step 1: Understand borrowing concept
Borrowing means using data without taking ownership, so the original owner keeps control.Step 2: Compare options
Copying or deleting data changes ownership or data state, which borrowing does not do.Final Answer:
Use data without taking ownership -> Option BQuick Check:
Borrowing = Use without ownership [OK]
- Confusing borrowing with copying
- Thinking borrowing deletes data
- Assuming borrowing creates multiple owners
x in Rust?Solution
Step 1: Identify immutable borrow syntax
Immutable borrow uses&xto create a reference without allowing changes.Step 2: Check other options
&mut xis mutable borrow,*xdereferences, andxmoves ownership.Final Answer:
let y = &x; -> Option AQuick Check:
Immutable borrow = &x [OK]
- Using &mut for immutable borrow
- Dereferencing instead of borrowing
- Assigning ownership instead of reference
let mut s = String::from("hello");
let r1 = &s;
let r2 = &s;
println!("{} and {}", r1, r2);
let r3 = &mut s;
println!("{}", r3);Solution
Step 1: Analyze immutable borrows
Two immutable borrowsr1andr2are allowed simultaneously.Step 2: Check mutable borrow after immutable borrows
Trying to create mutable borrowr3while immutable borrows exist causes compile error.Final Answer:
Compile error due to mutable borrow while immutable borrows exist -> Option CQuick Check:
Mutable borrow disallowed with active immutable borrows [OK]
- Assuming mutable borrow works after immutable borrows
- Expecting runtime error instead of compile error
- Confusing output with allowed borrows
let mut data = vec![1, 2, 3];
let r1 = &mut data;
let r2 = &data;
r1.push(4);
println!("{:?}", r2);Solution
Step 1: Check borrows
Variabler1is a mutable borrow,r2is an immutable borrow of the same data.Step 2: Understand borrowing rules
Rust forbids immutable borrow while mutable borrow is active, causing a compile error.Final Answer:
Cannot borrow data as immutable while mutable borrow exists -> Option DQuick Check:
Immutable borrow disallowed during mutable borrow [OK]
- Thinking push is invalid inside mutable borrow
- Ignoring borrow conflict between r1 and r2
- Assuming printing with {:?} causes error
Solution
Step 1: Check function signature for mutable borrow
Function needs&mut Vec<i32>to modify vector by pushing a number.Step 2: Verify function body and usage
fn add_and_len(v: &mut Vec<i32>, n: i32) -> usize { v.push(n); v.len() } let mut nums = vec![1,2]; let length = add_and_len(&mut nums, 3); pushes number then returns length correctly; usage passes mutable reference.Step 3: Analyze other options
fn add_and_len(v: &Vec<i32>, n: i32) -> usize { v.push(n); v.len() } let mut nums = vec![1,2]; let length = add_and_len(&nums, 3); uses immutable borrow but tries to push (not allowed). fn add_and_len(v: Vec<i32>, n: i32) -> usize { v.push(n); v.len() } let nums = vec![1,2]; let length = add_and_len(nums, 3); takes ownership, not borrow. fn add_and_len(v: &mut Vec<i32>, n: i32) -> usize { v.len(); v.push(n) } let mut nums = vec![1,2]; let length = add_and_len(&mut nums, 3); returns length before push, so length is old value.Final Answer:
fn add_and_len(v: &mut Vec<i32>, n: i32) -> usize { v.push(n); v.len() } let mut nums = vec![1,2]; let length = add_and_len(&mut nums, 3); -> Option AQuick Check:
Mutable borrow needed to modify vector [OK]
- Using immutable borrow when modifying data
- Taking ownership instead of borrowing
- Returning length before modification
Ready to prove it?
Pass the test and this pattern moves unknown
