We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Closures and Iterators step 9 of 28
map, filter, filter_map: parse host:port lines
Pick the usable ports out of a list of "host:port" lines.
pub fn parse_ports(lines: Vec<String>) -> Vec<u16>
Keep a line only if all three hold:
- it splits at a colon into a host part and a port part;
-
the whole port part parses as a
u16; - the port is at least 1024 (below that is the privileged range).
Keep the surviving ports in input order, duplicates included. Everything else is silently dropped — no errors, no placeholders.
["localhost:8080", "example.com:80", "bad", "a:b"] gives [8080].
Split at the first colon and require the entire remainder to parse. That
makes "a:b:c" and "a::8080" fail on their own, with no extra check: the
remainders "b:c" and ":8080" are not numbers.
The three workhorses
These three cover most iterator code you will ever write or read.
| Adapter | Closure returns | Item count | Item type |
|---|---|---|---|
map |
U |
unchanged |
T → U |
filter |
bool |
shrinks | unchanged |
filter_map |
Option<U> |
shrinks |
T → U |
filter_map is not a convenience wrapper. It is the right tool whenever
deciding whether to keep an item and computing the kept value are the same
work — which is exactly what parsing is. You cannot know whether "8080"
is a valid port without parsing it, and once you have parsed it you would
rather not throw the number away and parse it again.
Every filter_map closure returns Option<U>. Some(v) means “keep, and
here is the value”; None means “drop this one”.
? inside the closure is the trick worth stealing
A filter_map closure returns Option, and ? works in any function or
closure returning Option. So a chain of fallible steps collapses:
.filter_map(|line| {
let (_host, port) = line.split_once(':')?; // None -> drop
let port: u16 = port.parse().ok()?; // Err -> drop
// ... one more condition to go
})
split_once already returns Option<(&str, &str)>. parse returns
Result, so .ok() converts it to Option and ? does the rest. Three
early exits, no nesting, no match.
For the last condition, bool::then_some turns a predicate into an Option:
(port >= 1024).then_some(port). Use then instead if the value is
expensive to build — then takes a closure and is lazy, then_some takes a
value and is eager. Clippy’s unnecessary_lazy_evaluations will tell you off
for using then(|| x) where then_some(x) would do.
parse needs to be told what to parse into
let port = "8080".parse(); // E0283: type annotations needed
str::parse is generic over its return type:
fn parse<F: FromStr>(&self) -> Result<F, F::Err>. Nothing in
"8080".parse() says what F is, so inference has nothing to work with.
Two ways to say it:
let port: u16 = s.parse().ok()?; // typed binding
let port = s.parse::<u16>().ok()?; // turbofish
The ::<> is the turbofish, and this is the first of many places you
will need it. Item 9.21 is the general rule.
Parsing is strict, and the hidden cases lean on it: " 8080" fails
(whitespace is not trimmed), "8080 " fails, "-1" fails (u16 is
unsigned), "65536" fails (out of range), and "08080" succeeds as
8080 — leading zeros are fine.
The starter passes every test and is still rejected
Read it. It is the long way round, and it is what a filter_map-less mind
produces:
.map(|line| line.split_once(':'))
.filter(|x| x.is_some())
.map(|x| x.unwrap())
It works. It is also two clippy errors:
error: `filter` for `Some` followed by `unwrap` [clippy::option_filter_map]
error: `filter` for `Ok` followed by `unwrap` [clippy::result_filter_map]
Both are default-on, so both are graded. And clippy is right for a reason
worth internalising: filter(is_some) then map(unwrap) reintroduces a
panic path that the type system had already eliminated. The two lines are
correct only because they are adjacent. Move one, add an adapter between
them, and you have a live unwrap on a None. filter_map makes that
impossible to write.
While you are in there, filter(p).next() should be find(p)
(filter_next, also default-on), and .map(|x| x) should be nothing at
all (map_identity).
Write it as a single filter_map chain.
Grade is compile + tests + clippy -D warnings.
Loading visualization…
Stuck?
Rust reference solution
Sign in to attempt this problem and reveal the reference solution.