Ownership and functions in Rust - Time & Space Complexity
When we use ownership in Rust functions, it affects how many steps the program takes to run.
We want to see how the program's work changes as the input size grows when ownership moves between functions.
Analyze the time complexity of the following code snippet.
fn takes_ownership(s: String) {
println!("{}", s);
}
fn main() {
let data = String::from("hello");
takes_ownership(data);
}
This code moves ownership of a string to a function, which then prints it.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: Calling the function once with ownership moved.
- How many times: Exactly one time in this example.
As the size of the string grows, the time to print grows roughly in proportion to the string length.
| Input Size (n) | Approx. Operations |
|---|---|
| 10 | About 10 steps to handle the string data |
| 100 | About 100 steps |
| 1000 | About 1000 steps |
Pattern observation: The work grows directly with the size of the input string.
Time Complexity: O(n)
This means the time to run grows in a straight line as the input string gets bigger.
[X] Wrong: "Moving ownership is free and does not depend on input size."
[OK] Correct: Moving ownership of a large string is actually a constant time operation because Rust moves the pointer, length, and capacity without copying the data. However, printing the string takes time proportional to its length.
Understanding how ownership affects time helps you explain Rust code clearly and shows you know how data moves in programs.
"What if the function borrowed the string instead of taking ownership? How would the time complexity change?"
Practice
Solution
Step 1: Understand ownership transfer in Rust functions
When a function takes ownership, it means the variable's ownership moves to the function.Step 2: Effect on original variable after function call
Since ownership moved, the original variable cannot be used anymore after the call.Final Answer:
The original variable can no longer be used after the function call. -> Option DQuick Check:
Ownership transfer = original variable unusable [OK]
- Thinking ownership is shared, not moved
- Assuming variable is copied automatically
- Confusing borrowing with ownership transfer
Solution
Step 1: Identify borrowing syntax for string slices
Borrowing a string slice uses &str type, which is a reference to a string slice.Step 2: Check each signature for borrowing without ownership
fn print_name(name: &str) { println!("{}", name); } uses &str, which borrows without taking ownership. fn print_name(name: &String) { println!("{}", name); } borrows a String reference but &str is more idiomatic for slices.Final Answer:
fn print_name(name: &str) { println!("{}", name); } -> Option CQuick Check:
Borrow string slice = &str parameter [OK]
- Using String parameter takes ownership
- Confusing &String with &str
- Returning ownership when not needed
fn take_ownership(s: String) {
println!("Inside function: {}", s);
}
fn main() {
let s = String::from("hello");
take_ownership(s);
println!("Outside function: {}", s);
}Solution
Step 1: Analyze ownership transfer in function call
The function take_ownership takes ownership of s, so s is moved into the function.Step 2: Check usage of s after function call
After the call, s is no longer valid in main, so using it causes a compile error.Final Answer:
Inside function: hello Compile error: use of moved value 's' -> Option AQuick Check:
Ownership moved, use after move = compile error [OK]
- Assuming variable is copied automatically
- Expecting output after ownership moved
- Ignoring Rust's ownership rules
fn calculate_length(s: String) -> usize {
s.len()
}
fn main() {
let s1 = String::from("hello");
let len = calculate_length(s1);
println!("The length of '{}' is {}.", s1, len);
}Solution
Step 1: Identify ownership issue in function call
calculate_length takes ownership of s1, so s1 is moved and invalid after call.Step 2: Fix by borrowing s1 instead of moving
Change function parameter to &String and pass &s1 to avoid moving ownership.Final Answer:
Error: s1 is moved into calculate_length; fix by passing &s1 instead. -> Option AQuick Check:
Pass reference to avoid moving ownership [OK]
- Trying to return reference to local variable
- Ignoring ownership move on function parameters
- Misunderstanding borrowing syntax
Solution
Step 1: Understand ownership and return requirements
The function must take ownership of the vector and return ownership back after doubling elements.Step 2: Analyze each option for ownership and functionality
fn double_vec(v: Vec<i32>) -> Vec<i32> { v.iter().map(|x| x * 2).collect() } takes ownership (Vec<i32>), creates a new doubled vector, and returns it, satisfying requirements.Final Answer:
fn double_vec(v: Vec<i32>) -> Vec<i32> { v.iter().map(|x| x * 2).collect() } -> Option BQuick Check:
Take ownership, return new doubled vector [OK]
- Borrowing vector but not returning ownership
- Not returning anything from function
- Modifying borrowed vector without ownership
Ready to prove it?
Pass the test and this pattern moves unknown
