We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Ownership I: Moves, Copy, Clone, Drop step 1 of 20
The three rules of ownership
Return a name twice — once shouted, once whispered.
pub fn shout_twice(name: String) -> Vec<String>
"Ada" becomes ["ADA", "ada"]. That is the whole algorithm. The reason this
is the first problem of the track is that the starter does not compile, and
the error it produces is the single most important error in Rust.
The three rules
Every rule Rust enforces about memory is downstream of three sentences:
- Each value in Rust has an owner.
- There can only be one owner at a time.
- When the owner goes out of scope, the value is dropped.
Most beginners can recite those and still read the borrow checker as bureaucracy. So it is worth saying plainly what problem they solve.
In C, a char* tells you nothing about who frees it. Every C codebase invents a
convention — a comment, a naming scheme, a doc block — and every C codebase has
bugs where the convention was misread: two frees of the same pointer, a read
after a free, or a buffer nobody ever freed. In a garbage-collected language the
problem is handled by never freeing anything until a runtime proves nothing can
reach it, which costs you a runtime, pauses, and the ability to know when
anything is released.
Rust’s three rules are a third answer: put the convention in the type system so the compiler checks it. Rule 2 is what makes it decidable. If a value could have two owners, “when does this get freed?” would need whole-program analysis. With exactly one owner, the answer is local and mechanical — and so is the check.
What “moved” means
Write this:
let first = String::from("hello");
let second = first;
Nothing is copied on the heap. String is a three-word struct on the stack —
a pointer, a length, a capacity — and those three words are memcpy’d into
second. Now two variables would name the same heap buffer, which rule 2
forbids, so Rust invalidates the source: first is no longer usable. The
value moved.
That is why the error is called “use of moved value” and why the compiler keeps
telling you a type “does not implement the Copy trait” — for a type that does,
the source stays valid, because copying the bytes really does produce a second
independent value. String is not such a type: its bytes contain a pointer, and
two owners of the same pointer is exactly the situation rule 2 exists to prevent.
::: question Here are two versions of the same idea. Predict which compiles.
// (a)
pub fn shout_twice(name: String) -> Vec<String> {
vec![name.to_uppercase(), name.to_lowercase()]
}
// (b)
fn upper(s: String) -> String { s.to_uppercase() }
fn lower(s: String) -> String { s.to_lowercase() }
pub fn shout_twice(name: String) -> Vec<String> {
vec![upper(name), lower(name)]
}
(a) compiles. (b) does not — and that is not a bug in (a), it is the whole lesson.
str::to_uppercase has the signature fn to_uppercase(&self) -> String. That
& means the method only looks at the string; it never takes ownership.
name is untouched by the first call, so it is still there for the second.
upper takes String — no &. Calling it hands the value over for good. The
second call has nothing left to hand over, and rustc says so:
error[E0382]: use of moved value: `name`
|
| vec![upper(name), lower(name)]
| ---- ^^^^ value used here after move
| |
| value moved here
You will meet this pair — a &self method that leaves the value alone versus a
by-value function that consumes it — thousands of times. Reading a signature and
knowing instantly which one you are looking at is the skill this whole track
builds.
:::
Your job
Make the starter compile without changing upper or lower. You have exactly
three moves available, and only one of them works here:
- Give it away and never use it again. Fine when one call is enough; not enough here, because both calls need the name.
-
Have the function hand it back.
fn upper(s: String) -> (String, String)would work — but you may not change those signatures. -
Make a second value.
StringimplementsClone, andname.clone()allocates a fresh heap buffer holding the same bytes. Two values, two owners, no rule broken.
Use the third. Cloning is explicitly allowed in this track — it is training
wheels we take off deliberately in Track 3, once you have & to replace it
with. What matters now is that you can say out loud what a clone costs: one
allocation and one memcpy of the whole buffer, every time.
You may be tempted to write &name because rustc’s help text suggests borrowing.
Resist it for now. This track exists to exhaust move / clone / hand-it-back
first, so that when & arrives you already know exactly which pain it removes.
Watch out
Two of the test cases use non-ASCII text, and one of them uppercases ß into
SS — two characters where there was one. Anything that reaches for
as_bytes() and flips bit 5 will get these wrong. to_uppercase and
to_lowercase do full Unicode case mapping; use them.
Remember the grade is compile + tests + clippy -D warnings.
Loading visualization…
Stuck?
Rust reference solution
Sign in to attempt this problem and reveal the reference solution.