Step 6 of 6
Closures at work: sorting, filtering, defaults
Most closures you'll write at work are tiny arguments to standard library methods. You've used several of these methods already; now you know what they take.
struct Ticket {
id: u32,
title: String,
priority: u8,
open: bool,
}
fn main() {
let mut tickets = vec![
Ticket { id: 7, title: String::from("Login fails"), priority: 1, open: true },
Ticket { id: 3, title: String::from("Typo on homepage"), priority: 3, open: true },
Ticket { id: 9, title: String::from("Old bug"), priority: 2, open: false },
Ticket { id: 4, title: String::from("Slow search"), priority: 1, open: true },
];
tickets.retain(|t| t.open); // keep only open tickets
tickets.sort_by_key(|t| (t.priority, t.id)); // by priority, then by id
for t in &tickets {
println!("#{} [P{}] {}", t.id, t.priority, t.title);
}
let top = tickets.first().map(|t| t.title.to_uppercase());
println!("{}", top.unwrap_or_else(|| String::from("nothing to do")));
}
#4 [P1] Slow search
#7 [P1] Login fails
#3 [P3] Typo on homepage
SLOW SEARCH
| Method | The closure gets | and returns | Bound |
|---|---|---|---|
v.retain(f) |
&T |
bool: keep this element? |
FnMut |
v.sort_by_key(f) |
&T |
a key to sort by (anything Ord: numbers, tuples, Strings) |
FnMut |
v.sort_by(f) |
&T, &T |
an Ordering, usually from a.cmp(b) |
FnMut |
opt.map(f) |
the value inside Some |
the new value | FnOnce |
opt.unwrap_or_else(f) |
nothing | the fallback value | FnOnce |
map.entry(k).or_insert_with(f) |
nothing | the value to insert | FnOnce |
Tricks worth knowing:
- Several sort keys: a tuple key
(t.priority, t.id)sorts by the first field, then breaks ties with the second. - Descending order: swap
aandb:v.sort_by(|a, b| b.score.cmp(&a.score)). - Combining comparisons:
Orderinghas.then(...):b.score.cmp(&a.score).then(a.name.cmp(&b.name))means "highest score first, and for equal scores, by name". - Lazy defaults:
unwrap_or(expensive())always runsexpensive();unwrap_or_else(|| expensive())only runs it when the value is missing. Clippy'sor_fun_calllint points this out (it's off by default; turn on theclippy::nurserygroup to get it). - Function names instead of closures: when a closure only calls a function, pass the function itself:
map.entry(key).or_insert_with(Vec::new)instead of|| Vec::new(). Clippy'sredundant_closurelint suggests this.
Common mistakes
- A sort key that borrows from the element.
sort_by_keycan't return a reference into the elements it's moving around:
struct Player {
name: String,
score: u32,
}
fn main() {
let mut players = vec![
Player { name: String::from("Zed"), score: 3 },
Player { name: String::from("Amy"), score: 5 },
];
players.sort_by_key(|p| &p.name);
println!("{} {}", players[0].name, players[0].score);
}
error: lifetime may not live long enough
Use players.sort_by(|a, b| a.name.cmp(&b.name)) instead (no copies), or sort_by_key(|p| p.name.clone()) (allocates a String per comparison).
- Sorting by an
f64key. Floats aren'tOrd(what's the order ofNaN?), sosort_by_key(|i| i.price)fails withthe trait bound `f64: Ord` is not satisfied. Usesort_by(|a, b| a.price.total_cmp(&b.price)):total_cmpgives floats a complete order.
Your task
Write fn leaderboard(players: &mut Vec<Player>, min_score: u32). It removes every player whose score is below min_score, then sorts the rest by score, highest first; players with the same score are sorted by name, A to Z. Use retain and a sort method with closures, no loops.
Challenge 2
Write fn tidy_tags(tags: &mut Vec<String>) that cleans up a list of tags: remove empty tags, sort the rest by length (shortest first) with equal lengths in alphabetical order, then remove duplicates. For example ["rust", "", "go", "c", "rust", "zig"] becomes ["c", "go", "zig", "rust"]. No loops.
Challenge 3
Write a program that reads shopping items from input, one per line, as name price (for example lamp 19.99), until the input ends. Keep only the items that cost at most 20.00, sort them by price (cheapest first), and print each one as name: price with two decimals. If nothing is left, print nothing affordable.
Prices are f64, so sort with sort_by and total_cmp, and filter with retain.