We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Closures and Iterators step 11 of 28
Split a message at its first 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 when line.trim().is_empty() is true. This includes empty
strings and strings containing only Rust's Unicode whitespace, such as spaces,
tabs or non-breaking spaces. Use trimming only to decide where to split:
preserve the original contents and order of every retained string.
Return all lines before the first blank in the header and all lines after it in the body. Discard exactly that one separator. Later blank lines belong to the body and must remain unchanged.
input: ["Subject: hi", "From: me", "", "Hello", "", "World"]
header: ["Subject: hi", "From: me"]
body: ["Hello", "", "World"]
| Input | Header | Body |
|---|---|---|
["a", "b"] |
["a", "b"] |
[] |
["", "a"] |
[] |
["a"] |
["a", "", "", "b"] |
["a"] |
["", "b"] |
[" title ", " ", " text "] |
[" title "] |
[" text "] |
[] |
[] |
[] |
A trailing separator leaves an empty body. If there is no separator, every line belongs to the header.
Use take_while, skip_while and skip to build the two halves. Replace the
starter's length-based skip and blank-line filter. Do not use index loops,
length arithmetic or position() for this exercise.
Stop at a boundary or filter each item?
These adapters behave differently as their output is consumed:
| Adapter | Behavior |
|---|---|
filter(predicate) |
Keeps each item whose predicate is true. |
take_while(predicate) |
Yields a prefix, ending at the first false result. |
skip_while(predicate) |
Skips a prefix, then yields the first failing item and everything after it. |
The starter's filter removes every blank in the body. For ["a", "", "", "b"],
it incorrectly returns ["b"] as the body. A predicate that tests only whether
the current line is blank cannot distinguish the separator from later blanks.
A stateful filter could track that distinction, but the required adapters
express the boundary directly.
Drop only the separator
Start each chain with a fresh lines.iter():
- For the header, take lines while they are not blank.
- For the body, skip lines while they are not blank, then skip one more item.
skip_while pulls the first blank from the source and yields it. The following
skip(1) discards that separator. After that, skip_while no longer tests its
predicate, so later blanks pass through. Skipping an exhausted iterator is safe.
By comparison, take_while consumes its first failing item without yielding it.
Reusing the same source iterator after that would already start after the
separator. by_ref() alone does not change this behavior. The two fresh
iterators here avoid sharing that consumption state.
See the iterator documentation for more on consuming the boundary item.
Build owned results
lines.iter() yields references to strings. Put .cloned() after selecting
the wanted lines, then collect into Vec<String>. This copies retained strings
for the owned result without cloning the skipped strings. The function owns
the input, so another design could move strings; cloning is convenient for
these two borrowed passes.
Passing this exercise requires compilation, the test cases and Clippy with warnings denied.
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.