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 in uppercase, once in lowercase.
pub fn shout_twice(name: String) -> Vec<String>
For example, "Ada" becomes ["ADA", "ada"]. The case conversion is the easy
part. The starter code does not compile, and the exercise is about reading
that compiler error and fixing it.
The contract
namecan be any string, including the empty string and non-ASCII text.- Return a vector with exactly two elements, in this order: the result of
str::to_uppercaseonname, then the result ofstr::to_lowercaseonname. - Those methods apply Unicode's default case mappings, which do not depend on
language or locale. A conversion can change the length of a string:
"ß"uppercases to"SS". Characters without case, such as digits and spaces, are unchanged. - An empty
namegives["", ""].
The three rules
The Rust book states ownership as three rules:
- 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 (its cleanup
code runs and, for a
String, its heap buffer is freed).
An owner is usually a variable, a struct field or a collection element that
holds the value. Borrowing with & is a separate set of rules that a later
exercise covers; this exercise uses only moves and clones.
These rules address a real problem. In C, a char* does not say who is
responsible for freeing the memory it points to. Programs rely on conventions
in comments or names, and mistakes produce double frees, reads after a free,
or memory that is never freed. Garbage-collected languages avoid freeing
memory by hand: a runtime frees memory once it determines that nothing can
reach it, which means you do not control exactly when that happens.
Rust takes a third approach. Ownership is part of the type system, so the
compiler checks it. Because each value has exactly one owner, the compiler can
decide when to drop it by looking at the owner's scope, without tracking every
place the value might be shared. (Types such as Rc provide shared ownership
when you need it, by keeping a reference count at run time. Each Rc handle
is still an ordinary value with one owner.)
What "moved" means
Consider:
let first = String::from("hello");
let second = first;
The text "hello" is not copied. A String value consists of three fields: a
pointer to a heap buffer holding the bytes, a length and a capacity. The
assignment copies those three fields into second (the optimizer may avoid
even that copy). If first stayed usable, two variables would own the same
heap buffer and both would try to free it, so rule 2 applies: first can no
longer be used. The value has moved to second.
Some types, such as integers and bool, implement the Copy trait. For those,
copying the bits produces a second independent value, so the source stays
usable after an assignment or a call. String does not implement Copy,
because a copy of its bits would be a second owner of the same heap buffer.
That is why rustc's error message mentions Copy.
Check your understanding Here are two versions of the same idea. Predict which compiles. Show answer Hide answer
// (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.
str::to_uppercase has the signature fn to_uppercase(&self) -> String. The
&self means the method receives a reference to the string and does not take
ownership of it. (Calling it on a String works because a String can be
used where a &str is expected.) After the first call, name still owns its
value, so the second call can use it.
upper takes s: String, with no &. Calling upper(name) moves the value
into upper, which drops it when it returns. The second call, lower(name),
uses a variable whose value has already moved, and rustc 1.98 reports (abridged,
line numbers removed):
error[E0382]: use of moved value: `name`
|
| pub fn shout_twice(name: String) -> Vec<String> {
| ---- move occurs because `name` has type `String`, which does not implement the `Copy` trait
| vec![upper(name), lower(name)]
| ---- ^^^^ value used here after move
| |
| value moved here
The full message also notes that upper could borrow its parameter instead,
and suggests cloning. Whether a function takes &T or T tells you whether a
call leaves your value usable or consumes it, so it is worth checking the
signature whenever you see this error.
Your job
Make the starter compile and return the correct vector, keeping upper and
lower unchanged and calling each of them once. (The tests check only the
returned vector; keeping the helpers is what makes this an ownership
exercise.) In general there are three ways to deal with a move:
- Give the value away and stop using it. This works when one call is enough. Here both calls need the name.
- Have the function return the value. For example,
fn upper(s: String) -> (String, String)could return the uppercase text and the original string. This exercise does not allow changing the helpers' signatures. - Make a second value.
StringimplementsClone, andname.clone()creates a newStringwith its own heap buffer containing the same bytes. There are now two values with one owner each, so no rule is broken.
Use the third option. The cost of a clone is a new heap allocation (none for
an empty string) plus a copy of all the string's bytes, and you pay it each
time you call clone.
rustc's note suggests changing upper to borrow its parameter (for example,
fn upper(s: &str) -> String). That is often the better design, but it changes the helper's signature, which this exercise
does not allow. Borrowing is covered later.
Watch out
Two of the test cases use non-ASCII text, and one of them uppercases ß into
SS. Methods that work byte by byte, such as to_ascii_uppercase or flipping
bits in as_bytes(), leave non-ASCII letters unchanged and fail these cases.
Use to_uppercase and to_lowercase.
Your code must compile, pass the tests and pass cargo clippy -- -D warnings.
Your code, saved here
Sign in to access your accepted solutions and saved draft history. You can keep reading the lesson without an account.
Sign in to your accountStuck?
Rust reference solution
Sign in to track your attempts toward unlocking the reference solution.