We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Closures and Iterators step 5 of 28
iter, iter_mut, into_iter: all three, one Vec
Take one Vec<String> and produce three results from it, in this order.
pub fn triple(words: Vec<String>) -> (usize, Vec<usize>, Vec<String>)
.0: the total length in bytes of the words as they arrived, computed while the vector is still unchanged..1: uppercase every word in place (replace each element of the vector with its uppercase form), and return each word's new length in bytes, in the original order..2: the uppercased words from step 2, moved out of the vector as ownedStrings, in reverse order.
For ["hello", "world"] the result is (10, [5, 5], ["WORLD", "HELLO"]).
For [] it is (0, [], []).
Exact contract
- The vector may be empty, and individual strings may be empty.
- Every length is a UTF-8 byte count, the value returned by
str::len, not a count of characters. - Uppercase with
str::to_uppercase. It applies Unicode's default case mapping, which does not depend on a language or locale. Do not trim, normalize or otherwise change the strings. .1has one entry per input word, in input order..2contains every uppercased word, last input word first.
The three steps are ordered so that each one fits a different way of iterating over the vector: step 1 only reads, step 2 changes elements while the vector stays in place, and step 3 takes ownership of the strings.
Three ways to iterate over a Vec
A Vec offers three iterator methods. They differ in how they access the
vector (shared borrow, mutable borrow or by value) and therefore in the type
of each item the iterator yields, called Item:
| Call | Takes the vector as | Item for a Vec<String> |
Afterwards |
|---|---|---|---|
v.iter() |
&v (shared borrow) |
&String |
v is unchanged and usable |
v.iter_mut() |
&mut v (mutable borrow) |
&mut String |
v is usable and may be modified |
v.into_iter() |
v (by value, a move) |
String |
v has been moved and cannot be used |
Arrays and VecDeque offer the same three. A slice [T] has iter and
iter_mut, but it does not own its elements, so it cannot hand them out by
value: calling into_iter on a &[T] yields &T. Not every collection
offers all three:
HashSet and BTreeSet have no iter_mut, because changing an element
could invalidate where the set stored it, and HashMap::iter_mut yields
(&K, &mut V), so only the values can be changed.
Choosing the wrong form produces several common errors. Some examples, all checked with Rust 1.98:
- Calling
.iter()on aVec<String>and collecting intoVec<String>giveserror[E0277]: a value of type Vec<String> cannot be built from an iterator over elements of type &String. - Writing
.map(|s| *s)over.iter()tries to move aStringout of a shared reference:error[E0507]: cannot move out of *s which is behind a shared reference. - In
v.iter().filter(|x| ...),filterpasses each item by reference, soxis a&&i32for aVec<i32>.
A useful question before writing a loop is whether you need to read the elements, change them, or take them. The answer selects the method, and the method determines the item type.
The starter does not compile
It begins with this loop:
for w in words {
total += w.len();
}
and then uses words again. The compiler reports:
error[E0382]: borrow of moved value: `words`
A for loop calls IntoIterator::into_iter on the expression after in.
For a Vec<String> value, that is Vec::into_iter, which takes the vector by
value. Each w is therefore an owned String, and after the loop words has
been moved and cannot be used.
To read without moving, iterate over a reference: for w in &words or
words.iter(). These produce the same iterator type, because
IntoIterator for &Vec<T> is implemented by calling iter(). Clippy's
into_iter_on_ref lint, which warns by default, flags (&words).into_iter()
and suggests words.iter() for the same reason.
Mutating in place needs a mutable binding
Step 2 uses iter_mut, which takes &mut self. A mutable borrow of a local
variable requires the variable to be declared mut, and the parameter
words: Vec<String> is not, so you will see:
error[E0596]: cannot borrow `words` as mutable, as it is not declared as mutable
Declare the parameter as mut words: Vec<String>. The mut on a by-value
parameter is not part of the function's signature as seen by callers: they
still pass a Vec<String> and nothing changes for them. It only makes the
local binding mutable, like let mut. Because the function owns the vector,
it may mutate it.
Inside the closure (or loop) that iter_mut drives, w has type
&mut String. To replace the string it points to, assign through the
reference: *w = w.to_uppercase();. Without the *, the assignment tries
to store a String in the variable w, whose type is &mut String, and
the compiler reports E0308 (mismatched types).
Uppercasing can change byte lengths
str::to_uppercase maps each character to its full Unicode uppercase form,
which can contain more or fewer bytes than the original. The hidden tests
include:
"fire": 5 bytes, because thefiligature (U+FB01) is one 3-byte character. It uppercases to"FIRE", which is 4 bytes."straße": 7 bytes.ß(2 bytes) uppercases toSS(2 bytes), so"STRASSE"is also 7 bytes, but it has one more character."café": 5 bytes, uppercased to"CAFÉ", also 5 bytes.
Because of the first case, the step 1 total must be computed from the original strings and the step 2 lengths from the uppercased ones. Computing both from the same version of the strings compiles but gives a wrong answer.
Cloning is not needed
Cloning the vector so you can call into_iter on each copy would pass the
tests, but it copies every string for no benefit. Borrowing for step 1,
mutably borrowing for step 2 and moving for step 3 solves the task without
cloning. (The uppercase strings and the two output vectors are still new
allocations.) When a borrow error seems to call for clone, first check
whether a different iteration form gives the access you need.
Grading compiles your code, runs the tests and runs
cargo clippy -- -D warnings, so Clippy warnings count as failures.
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.