We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Ground Rules: Values, Types, Control Flow step 2 of 24
Shadowing: rebinding is not mutation
Count the whitespace-separated words in a string.
pub fn normalize(raw: &str) -> usize
" a b c " is 3. "" is 0. "single" is 1. Runs of whitespace count
as one separator, and leading or trailing whitespace does not create empty
words.
The starter does not compile. Fixing it is the lesson.
Two different things that look the same
let mut x = 5;
x = 6; // mutation: same binding, new value, SAME TYPE
let x = 5;
let x = "five"; // shadowing: a NEW binding, which may have a new type
The second form is called shadowing, and it is completely idiomatic Rust. It is not a typo, not a lint, and not a code smell. It is the normal way to express “the same conceptual thing, one step further along in its processing.”
The critical difference: mut keeps the type. let again does not have to.
A let mut raw: &str can never hold a SplitWhitespace, and the compiler says
so with E0308: mismatched types. A second let raw = … creates a brand new
binding that happens to reuse the name, and its type is inferred fresh.
Why this is worth teaching immediately after mut
Someone who has met mut but not shadowing will reach for mut whenever a
value needs to change at all — including when what they actually want is a
retyped rebinding. That produces mut bindings that did not need to be mutable
and, worse, a habit of forcing everything into one type early.
The idiomatic pipeline for this problem is three bindings with one name:
let raw = raw.trim(); // &str -> &str, whitespace stripped
let raw = raw.split_whitespace(); // &str -> an iterator over words
raw.count() // iterator -> usize
Each let shadows the previous one. Each has a different type. Nothing is
mutated. The parameter raw is itself shadowed by the first line — parameters
are bindings too.
The bits of the standard library you need
-
str::trimreturns a&strwith leading and trailing whitespace removed. It does not allocate: the result borrows the same bytes, just a shorter range of them. -
str::split_whitespacereturns a lazy iterator over the non-empty whitespace-separated chunks. Because it is lazy, nothing has been computed yet — the work happens when you consume it. -
Iterator::countconsumes the iterator and returns how many items it produced, as ausize.
Note that split_whitespace already skips empty chunks, which is why the
leading and trailing spaces in the examples do not produce phantom words. Its
cousin split(' ') does not do that, and would give you 5 for " a b c ".
Choosing between them is a real decision, not a detail.
A word of honesty about the gate
Every clippy lint about shadowing — shadow_unrelated, shadow_same,
shadow_reuse — lives in the restriction group, which is off here and which
clippy itself warns contains lints that contradict one another. So on this
problem the tools will give you no feedback at all about whether you have
shadowed too much.
That is deliberate, and the community is genuinely split on where the line is.
The workable rule: shadowing is good when the new binding is the same idea
further refined (raw the input, raw trimmed, raw split). It is bad when
the new binding is a different idea that happens to be nearby, because then
the reader who scrolls up finds the wrong definition. Judgement, not lint.
Stuck?
Rust reference solution
Sign in to attempt this problem and reveal the reference solution.