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 words in a string, where a word is a maximal run of non-whitespace characters.
pub fn normalize(raw: &str) -> usize
The contract
- "Whitespace" means a character with the Unicode
White_Spaceproperty, which is whatchar::is_whitespaceandstr::split_whitespaceuse. That includes space, tab, newline and carriage return, and also non-ASCII characters such as the no-break space U+00A0 and the ideographic space U+3000. A zero-width space (U+200B) is not whitespace, so it does not separate words. - Any run of one or more whitespace characters counts as a single separator. Leading and trailing whitespace does not create empty words.
- Every other character, including accented letters, digits and punctuation,
is part of a word.
"héllo, wörld!"has 2 words. - Return the count as a
usize. An empty string, or a string containing only whitespace, gives0.
Examples: " a b c " gives 3, "" gives 0, "single" gives 1, and
"a\tb\nc d" (with a tab and a newline) gives 4.
The starter does not compile. The task is to fix it by replacing an assignment
with a new let binding and dropping the mut that is then unused.
Mutation and shadowing
let mut x = 5;
x = 6; // mutation: same binding, new value, same type
let y = 5;
let y = "five"; // shadowing: a second binding named y, with its own type
The first pair is mutation. x is one binding declared mut, and the
assignment x = 6 stores a new value in it. A binding has one type for its
whole life, so the new value must have the same type as the old one.
The second pair is shadowing. The second let y declares a new binding
that reuses the name. From that line on, y refers to the new binding, and its
type is inferred from its own initializer. The first y was not changed; it is
just no longer reachable by that name. Shadowing inside a { } block lasts
only until the end of the block, after which the outer binding is visible
again. Neither binding in the second pair needs mut, because neither is
assigned to after it is created.
This is why the starter fails. let mut raw = raw.trim(); gives raw the
type &str. The next line tries to assign a SplitWhitespace iterator to that
same binding, and rustc rejects it with error[E0308]: mismatched types
("expected &str, found SplitWhitespace<'_>"). The starter also reports
error[E0599] on the last line, because raw is still a &str there and
&str is not an iterator, so it has no count method. Both errors go away
together once the second line declares a new binding instead of assigning.
Then remove the mut from the first line as well: nothing assigns to that
binding any more, so rustc warns "variable does not need to be mutable", and
the Clippy check run on submissions treats warnings as errors.
The pipeline
let raw = raw.trim(); // &str -> &str, outer whitespace removed
let raw = raw.split_whitespace(); // &str -> SplitWhitespace, an iterator
raw.count() // iterator -> usize
Each let shadows the previous binding, and each has a different type. The
function parameter raw is a binding too, so the first line shadows it. No
binding here is mutated, so none needs mut.
The trim step is not required for the result: split_whitespace already
ignores leading and trailing whitespace. It is kept here to show a chain of
three shadowed bindings; raw.split_whitespace().count() returns the same
number.
The standard library pieces
str::trimreturns a&strwith leading and trailing whitespace removed. The result borrows a shorter range of the same bytes, so no new string is allocated.str::split_whitespacereturns aSplitWhitespaceiterator over the non-empty pieces of the string separated by whitespace. Iterators are lazy: creating it does no splitting, and the pieces are found as the iterator is consumed.Iterator::countconsumes the iterator and returns the number of items it produced, as ausize.
A similar-looking method gives different answers. split(' ') splits on every
single space character and keeps the empty pieces between adjacent spaces and
at the ends, so " a b c ".split(' ').count() is 7, not 3. It also does
not split on tabs or newlines. split_ascii_whitespace splits only on ASCII
whitespace, so it treats a no-break space as part of a word.
When to shadow
Clippy's lints about shadowing (shadow_same, shadow_reuse and
shadow_unrelated) are all in the restriction group, which is not enabled
by default. Clippy's documentation says that group is not meant to be enabled
as a whole and that some of its lints contradict others. With the default
lints used for this exercise, Clippy will not comment on how you use shadowing.
A reasonable guideline: shadow when the new binding is the same value at a
later stage of processing, as with raw here (the input, then the trimmed
input, then its words). Use a new name when the value means something
different, because a reader looking for the earlier definition of the name
could otherwise find the wrong one.
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.