Move Semantics
In Python, assignment creates a new reference to the same object:
names = ["Alice", "Bob"]
guests = names # both point to the same list
guests.append("Charlie")
print(names) # ["Alice", "Bob", "Charlie"] — same list!
This works because Python uses reference counting and garbage collection. When no references remain, the GC cleans up.
Rust has no garbage collector. Instead, every value has exactly one owner. When you assign a value to another variable, ownership moves:
let names = vec!["Alice", "Bob"];
let guests = names; // ownership moves to guests
// println!("{:?}", names); // ERROR: names was moved
println!("{:?}", guests); // OK: guests owns the data
After the move, names is no longer valid. This isn't a limitation — it's how Rust guarantees memory safety without a GC. When guests goes out of scope, the memory is freed. No double-free bugs, no use-after-free.
Why moves matter
Moves prevent bugs that are common in other languages:
fn process(data: Vec<i32>) {
// data is dropped at end of function
}
let numbers = vec![1, 2, 3];
process(numbers);
// numbers is gone — can't accidentally use freed memory
In C, you might accidentally use numbers after process freed it. In Python, the GC keeps it alive. Rust catches the bug at compile time.
The move happens at assignment
The key insight: assignment transfers ownership. This applies to:
- let b = a;
- Passing to functions: foo(a);
- Returning from functions: return a;
Your Task
Implement functions that demonstrate move semantics:
take_ownership(items: Vec<String>) -> usize— takes ownership of a vector and returns its lengthmove_and_return(text: String) -> String— takes ownership and returns the value wrapped in brackets:"[text]"swap_ownership(a: String, b: String) -> (String, String)— takes two strings and returns them swapped:(b, a)
Example
let items = vec!["one".to_string(), "two".to_string()];
assert_eq!(take_ownership(items), 2);
// items is no longer valid here
let text = String::from("hello");
let result = move_and_return(text);
assert_eq!(result, "[hello]");
let (x, y) = swap_ownership("first".to_string(), "second".to_string());
assert_eq!(x, "second");
assert_eq!(y, "first");
Dive deeper: In our Rust Developer Cohort, the tokenizer produces a
Vec<Token>that moves into the parser — ownership transfer ensures the tokens can't be accidentally modified after handoff.
Further Reading
- The Rust Book — What Is Ownership? — the ownership rules
- The Rust Book — Move Semantics — how moves work