We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Ownership II: Borrowing and the Borrow Checker step 1 of 24
Move or borrow: the two ways to pass a value
Write two functions that both look at a string without taking it away from the caller.
pub fn char_count(text: &str) -> usize
pub fn shout(text: &str) -> String
char_count returns how many characters the text contains. shout
returns a new String: the text uppercased, with a single ! appended.
"" -> 0, "!"
"hi" -> 2, "HI!"
"héllo" -> 5, "HÉLLO!"
The real subject: that &
In Track 2 you learned that passing a value moves it — the caller hands it
over and can never touch it again. That is a strong guarantee, and it would be
intolerable if it were the only option. You cannot write a function called
char_count that destroys its argument.
So Rust has a second way to pass a value: you lend it. &T is a reference
— a borrowed view of a value someone else owns.
If you come from C or C++, resist the word “pointer”. A Rust reference is narrower and stronger than a pointer:
-
it is never null. There is no
if (p == NULL)in Rust. Absence isOption<&T>, a different type, and the compiler makes you handle it. -
it is always valid. The compiler will not let a reference outlive the
thing it points at. A dangling
&is not a bug you can write. - it is temporary by construction. Every reference has a region of the program over which it is live, and the compiler tracks it. This is the whole subject of this track.
-
it is not a copy.
&textdoes not duplicate the string’s bytes; it hands over a pointer and a length. Cheap, always.
Why &str and not &String
The signature is pinned to &str, and you should not “improve” it to
&String. &String says “I need a reference to a heap-allocated, growable
string specifically”. &str says “I need to look at some UTF-8 text, and I do
not care where it lives”. The second is strictly more useful: it accepts a
String, a string literal, a slice of a bigger string, all with no work at
the call site.
Clippy enforces this with the ptr_arg lint, and this problem is graded with
clippy -D warnings, so &String will fail the gate. Item 3.10 comes back to
this and explains the machinery (deref coercion) that makes it free.
Characters are not bytes
text.len() gives the length in bytes, because that is the number a
&str actually stores. "héllo" is five characters and six bytes: é is two
bytes in UTF-8. The test suite contains that exact case, so .len() will not
pass.
Counting characters means walking the text: .chars() yields one char per
Unicode scalar value. That is O(n), which is why len() is not it.
(Even chars().count() is not “how many things a human sees” — a family emoji
or a combining accent can be several chars. Track 5 goes into that. For this
problem, chars are what is asked for.)
The proof that nothing was consumed
The test harness calls char_count(&text) and then shout(&text) on the
same String. If either function took String by value, the second call
would be E0382: borrow of moved value. The fact that the harness compiles
at all is the proof that both of your functions only borrowed.
While you are writing shout, notice that you cannot move the text out of
the reference either — let owned = *text; is E0507: cannot move out of a
shared reference. A borrow lets you look, not take. You have to build a new
String, which is exactly what to_uppercase does.
One warning about uppercasing: it is not a per-byte operation and it is not
even length-preserving. German ß uppercases to SS — one character becomes
two. The tests here stay clear of that case, but do not carry away the idea
that uppercase is a cheap in-place flip. It is a table lookup over Unicode.
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.