We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Errors Are Values step 3 of 24
The Option combinator vocabulary
Look a user up by name and turn their optional score into a final answer — as one chain of combinators.
pub fn lookup_score(users: Vec<(String, Option<i32>)>, name: String) -> Option<i32>
Four steps, in order:
- find the first entry whose name matches;
-
take that entry’s score, which is itself an
Option<i32>; -
keep it only if it lies in
0..=100(inclusive at both ends); - double it.
Any step that comes up empty makes the whole thing None. So
[("ada", Some(42))] with "ada" gives Some(84), while
[("ada", None)], [("ada", Some(120))] and a name that is not in the list
all give None.
The starter works and is still rejected
Compile it. It passes every test case. Then run the gate: two lints fire.
error: manual implementation of `Option::filter`
error: using `Option.and_then(|x| Some(y))`, which is more succinctly
expressed as `map(|x| y)`
That second one, bind_instead_of_map, is the single most common beginner
confusion in this whole area, and it is worth stating the rule once, sharply:
-
maptakesT -> Uand gives youOption<U>. Your closure returns a plain value;mapre-wraps it. -
and_thentakesT -> Option<U>and gives youOption<U>. Your closure returns anOption;and_thendoes not re-wrap, it flattens.
So x.and_then(|n| Some(n * 2)) is map written the long way — you wrapped by
hand a thing that was about to be wrapped for you. And
x.map(|n| lookup(n)), where lookup returns an Option, gives you
Option<Option<_>> — the shape that makes people give up and write a match.
That one wants and_then.
Step 2 of this problem is exactly the and_then case: the entry you found is
(String, Option<i32>), so pulling the score out gives you an Option inside
an Option, and and_then is what collapses it.
The vocabulary, grouped by what it does
Not alphabetically — alphabetical lists of methods are how this API stays unmemorable. Group by intent and it is about fifteen things.
Ask a question — is_some, is_none, is_some_and(f) (1.70),
is_none_or(f) (1.82). All return bool.
Transform, staying inside Option — map(f), filter(p), and_then(f),
or_else(f), xor(other), zip(other) (1.46), unzip (1.66),
flatten (1.40), inspect(f) (1.76).
Leave Option behind — unwrap_or(default), unwrap_or_else(f),
unwrap_or_default(), map_or(default, f), map_or_else(d, f), ok_or(e),
ok_or_else(f).
Two details in that middle group bite people:
-
filterhands your closure a&T, not aT. So it isfilter(|x| *x > 0), or — as in the solution here —filter(|s| (0..=100).contains(s)), becauseRangeInclusive::containswants a reference anyway. If you writefilter(|x| x > 0)you will get E0308:&i32is noti32. -
orandor_elseare about theOption, not the value.a.or(b)keepsaif it isSome; it never looks inside.
E0593 — “closure is expected to take 1 argument” — is the error you get when
you hand a combinator a closure of the wrong arity, most often after
destructuring a tuple: find gives your closure one argument (a reference
to the tuple), so it is |(n, _)| …, one pattern, not |n, _| ….
Why the gate denies these three lints
bind_instead_of_map is on by default. manual_filter and manual_map are on
by default too, but they only fire on some of the shapes a hand-written match
can take — the starter uses shapes that do fire. The #![deny(…)] line at the
top of the file makes all three fatal rather than merely noisy, so that “I’ll
just write a match” is not an escape route. Leave that attribute in place;
it is part of the problem.
This is clippy at its best, incidentally. It is not enforcing a style preference — it is telling you that a language feature you already have does the thing you just spelled out by hand, and every one of its suggestions here shortens the code and makes it harder to get wrong.
Remember the grade is compile + tests + clippy -D warnings.
Loading visualization…
Stuck?
Rust reference solution
Sign in to attempt this problem and reveal the reference solution.