We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Closures and Iterators step 11 of 28
take_while and skip_while: split a message at the blank line
Split a list of lines into a header and a body at the first blank line.
pub fn header_body(lines: Vec<String>) -> (Vec<String>, Vec<String>)
A line is blank if it is empty or contains only whitespace. The blank line that does the splitting appears in neither half. Every other line — including every subsequent blank line — is kept exactly where it was.
["Subject: hi", "From: me", "", "Hello", "", "World"]
-> header: ["Subject: hi", "From: me"]
body: ["Hello", "", "World"]
Edge cases, all tested:
- no blank line at all → everything is header, body is empty;
- a leading blank line → header is empty;
- two blanks in a row → the first splits, the second is body content;
- empty input → two empty vectors.
Build both halves with take_while, skip_while and skip. No len()
arithmetic, no index loops, no position().
take_while is not filter, and this is where people lose an afternoon
Both take a predicate. Both return fewer items. They are completely different, and confusing them produces silently wrong output rather than a compile error — no rustc diagnostic, no clippy lint, just a test failure weeks later.
| What it does | |
|---|---|
filter(p) |
Tests every item. Keeps the ones that pass. Never stops early. |
take_while(p) |
Yields items while p holds. At the first failure it stops for good, and never calls p again. |
skip_while(p) |
Skips items while p holds. At the first failure it stops skipping, and yields everything from there on — including later items that would have passed p. |
Neither take_while nor skip_while ever resumes. They each flip once.
The starter builds the body with filter, and it gets the right answer
whenever there is exactly one blank line in the whole input — which is why
this bug survives casual testing. Feed it two blanks and the second one
disappears from the body. filter has no notion of “the first one”; it
cannot express this problem at all.
The item that take_while eats
Here is the detail that makes the skip(1) necessary, and that item 9.13
turns into a whole problem.
When take_while‘s predicate returns false, that item has already been
pulled out of the source iterator. take_while consumed it in order to
test it, and then dropped it on the floor. If you were sharing one iterator
between two phases, that element would simply be gone — this is why parsers
built on plain take_while mysteriously lose one token per phase, and why
by_ref and peekable exist.
Here you walk the input twice from the start, so nothing is lost. But
skip_while stops skipping at the blank line without consuming it, so it
yields the blank line first — hence the .skip(1) to drop the separator.
Get that off by one and case t3 (leading blank) will tell you immediately.
The rest of the family
-
take(n)/skip(n)— count-based, not predicate-based.skip(n)on a shorter iterator yields nothing rather than panicking. -
step_by(n)— everyn-th item, always including the first.(0..10).step_by(3)is0, 3, 6, 9.
Three of clippy’s lints here are deny-by-default, which is unusual and
tells you they catch real bugs rather than style:
iter_skip_zero (.skip(0) does nothing — you meant .skip(1)),
iterator_step_by_zero (.step_by(0) panics at runtime), and
infinite_iter (a chain that provably never terminates). Also default-on
and worth knowing: iter_skip_next — .skip(1).next() is .nth(1).
Cloning
Both halves are owned Vec<String>, built from a vector you were handed by
value. Filter first, clone the survivors — .take_while(..).cloned(), not
.cloned().take_while(..), or clippy’s iter_overeager_cloned will point
out that you cloned strings you then threw away.
Grade is compile + tests + clippy -D warnings.
Loading visualization…
Stuck?
Rust reference solution
Sign in to attempt this problem and reveal the reference solution.