Rust Ownership Exercises
12 scenarios of ownership covering moves, borrows, mutable/immutable conflicts, dangling references, and slices
I asked Claude to spin up 12 examples of ownership for me to read through and figure out if they'd work or not. I added notes on what the problems were.
Ownership & Borrowing Exercises
For each example, decide: does this compile? If not, figure out why (which rule it violates) before checking with rustc or the playground.
Example 1
fn main() {
let s1 = String::from("hello");
let s2 = s1;
println!("{}", s1);
}
This won't work because String doesn't implement Copy, so let s2 = s1; moves ownership from s1 to s2 instead of copying it. If you need a read-only borrow of s1 in s2 instead, use &, like let s2 = &s1.
Example 2
fn main() {
let x = 5;
let y = x;
println!("{} {}", x, y);
}
This works because x is a primitive type, so it is copied by value.
Example 3
fn takes_ownership(s: String) {
println!("{}", s);
}
fn main() {
let s = String::from("hello");
takes_ownership(s);
println!("{}", s);
}
String doesn't implement Copy, and takes_ownership takes its parameter by value (s: String), so calling takes_ownership(s) moves ownership of s into the function, then s is no longer valid back in main.
To avoid the move, borrow instead so change the param to s: &str and call takes_ownership(&s).
Example 4
fn borrows(s: &String) {
println!("{}", s);
}
fn main() {
let s = String::from("hello");
borrows(&s);
println!("{}", s);
}
This works because a reference is used.
Example 5
fn main() {
let mut s = String::from("hello");
let r1 = &s;
let r2 = &s;
println!("{} {}", r1, r2);
}
Since the reference for r1 is immutable and r2 is immutable this is ok, but just remove the mut from s since no mutations are happening.
Example 6
fn main() {
let mut s = String::from("hello");
let r1 = &s;
let r2 = &mut s;
println!("{} {}", r1, r2);
}
Now the difference is one immutable reference is live in r1 and a mutable reference is live in r2, and can't have the mutable and immutable borrows alive at the same time.
Example 7
fn main() {
let mut s = String::from("hello");
let r1 = &s;
println!("{}", r1);
let r2 = &mut s;
r2.push_str(" world");
println!("{}", r2);
}
This is ok, the immutable reference is dropped before the mutable reference is alive.
Example 8
fn dangle() -> &String {
let s = String::from("hello");
&s
}
fn main() {
let r = dangle();
println!("{}", r);
}
dangle creates s and returns a reference to it, but s goes out of scope and is dropped when the function returns, so the reference would be dangling. Rust rejects this at compile time — with fn dangle() -> &String, there's no way to know what the returned reference would be borrowed from, so the compiler actually errors with missing lifetime specifier before it even gets to check whether the reference would dangle.
The way to fix this is to not return a reference &String and return the owned String instead, so return s and change the function signature to fn dangle() -> String.
Example 9
fn main() {
let mut v: Vec<i32> = vec![1, 2, 3];
let first = &v[0];
v.push(4);
println!("{}", first);
}
first holds an immutable reference to the first element of v, and v.push(4) needs a mutable borrow of v while first is still alive, which is an aliasing/mutability violation, the same rule as seen in Example 6. If first were last used before the push (or even created after it), this would be fine.
Example 10
fn main() {
let mut s = String::from("hello world");
let word = &s[0..5];
s.clear();
println!("{}", word);
}
Same aliasing/mutability violation as Example 9, word is an immutable borrow of s and s.clear() needs a mutable borrow of s while word is still alive.
Example 11
fn main() {
let s1 = String::from("hello");
let s2 = s1.clone();
println!("{} {}", s1, s2);
}
This is ok.
Example 12
fn first_word(s: &str) -> &str {
let bytes = s.as_bytes();
for i in 0..bytes.len() {
if bytes[i] == b' ' {
return &s[0..i];
}
}
&s[..]
}
fn main() {
let mut s = String::from("hello world");
let word = first_word(&s);
s.push_str("!");
println!("{}", word);
}
This is ok up until s.push_str("!"), which needs a mutable borrow of s while word (an immutable borrow of s) is still alive and used later in the println!, aliasing/mutability violation, same as Examples 6, 9, and 10.