Ergonomics

#179 Jun 2026

179. Iterator::max_by_key — Find the Best Element Without a Manual Fold

Finding the “best” item in a collection — longest string, heaviest order, latest timestamp — is one of those tasks where a hand-rolled fold keeps showing up. Iterator::max_by_key does the same job in one call, and min_by_key is right there next to it.

The fold you keep writing

You have a slice of strings and want the longest one. The DIY version looks something like this:

1
2
3
4
5
6
7
8
9
let words = ["pear", "raspberry", "fig", "kiwi"];

let longest = words.iter().fold(None, |best, w| match best {
    None => Some(w),
    Some(b) if w.len() > b.len() => Some(w),
    other => other,
});

assert_eq!(longest, Some(&"raspberry"));

That’s a lot of code for a one-liner concept. max_by_key collapses the whole pattern:

1
2
3
let words = ["pear", "raspberry", "fig", "kiwi"];
let longest = words.iter().max_by_key(|w| w.len());
assert_eq!(longest, Some(&"raspberry"));

You hand it a closure that extracts the key — the thing you want to maximise — and it returns the item that produced the largest key.

Works on anything Ord

The key doesn’t have to be a number. It just has to implement Ord, which means strings, tuples, dates, your own types — anything totally ordered:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
#[derive(Debug, PartialEq)]
struct Order { id: u32, total_cents: u64 }

let orders = vec![
    Order { id: 1, total_cents: 1299 },
    Order { id: 2, total_cents: 4500 },
    Order { id: 3, total_cents: 800 },
];

let biggest = orders.iter().max_by_key(|o| o.total_cents);
assert_eq!(biggest, Some(&Order { id: 2, total_cents: 4500 }));

Tuples are where this really shines — you get multi-key sorting for free, because (A, B): Ord compares lexicographically:

1
2
3
4
5
let words = ["pear", "fig", "kiwi", "lime"];

// Longest word, then alphabetically last among ties.
let pick = words.iter().max_by_key(|w| (w.len(), *w));
assert_eq!(pick, Some(&"pear"));

Same trick with min_by_key — the entire _by_key family follows the same shape.

The tie-break rule, and why it matters

When two elements produce equal keys, max_by_key returns the last one and min_by_key returns the first. That asymmetry is in the docs and it bites people:

1
2
3
4
5
6
7
let nums = [3, 1, 4, 1, 5, 9, 2, 6, 5];

let max = nums.iter().max_by_key(|&&n| n);
let min = nums.iter().min_by_key(|&&n| n);

assert_eq!(max, Some(&9));
assert_eq!(min, Some(&1)); // the first 1, not the second

If you need a specific tie-break — “longest word, but earliest in the list when tied” — encode it in the key itself with a tuple, instead of relying on iteration order.

Floats need min_by / max_by

f32 and f64 don’t implement Ord (because NaN), so max_by_key won’t compile if your key is a float. Reach for max_by and pass a comparator instead:

1
2
3
4
5
6
let measurements = [1.2_f64, 3.4, 0.5, 2.8];

let biggest = measurements.iter()
    .max_by(|a, b| a.partial_cmp(b).unwrap());

assert_eq!(biggest, Some(&3.4));

Or wrap your floats in something like ordered_float::OrderedFloat and stay with max_by_key.

Takeaway

Any time you find yourself folding to track the “best so far,” check whether max_by_key or min_by_key says it in one line. The closure extracts the score; the iterator returns the winner. For ties you control the rule by shaping the key.

#176 Jun 2026

176. Option::as_deref — Stop Writing .as_ref().map(|s| s.as_str())

You have an Option<String>. The function next door takes Option<&str>. The chain you keep typing — .as_ref().map(|s| s.as_str()) — has a one-method replacement.

The pattern that keeps showing up

Borrowing the inside of an Option<String> looks like this:

1
2
3
4
let owned: Option<String> = Some("hello".to_string());

// Compare against a literal.
assert_eq!(owned.as_ref().map(|s| s.as_str()), Some("hello"));

as_ref() turns Option<String> into Option<&String>, then map reaches inside to pull out &str. Two methods, one closure, all just to borrow.

Option::as_deref collapses the whole thing:

1
2
let owned: Option<String> = Some("hello".to_string());
assert_eq!(owned.as_deref(), Some("hello"));

Same result, one call, no closure.

Why it works

as_deref is defined for any Option<T> where T: Deref. It calls Deref::deref on the inner value, so you get Option<&T::Target>:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
let s: Option<String> = Some("hi".to_string());
let v: Option<Vec<i32>> = Some(vec![1, 2, 3]);
let b: Option<Box<i32>> = Some(Box::new(7));

let s_ref: Option<&str> = s.as_deref();      // String -> str
let v_ref: Option<&[i32]> = v.as_deref();    // Vec<T> -> [T]
let b_ref: Option<&i32> = b.as_deref();      // Box<T> -> T

assert_eq!(s_ref, Some("hi"));
assert_eq!(v_ref, Some(&[1, 2, 3][..]));
assert_eq!(b_ref, Some(&7));

Anything that derefs works, including your own types — implement Deref and as_deref follows for free.

Where it actually saves you

The most common use is feeding a borrowed view into a function that wants the unsized form:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
fn greet(name: Option<&str>) -> String {
    match name {
        Some(n) => format!("hi {n}"),
        None => "hi stranger".to_string(),
    }
}

let stored: Option<String> = Some("alice".to_string());
assert_eq!(greet(stored.as_deref()), "hi alice");

let missing: Option<String> = None;
assert_eq!(greet(missing.as_deref()), "hi stranger");

The stored value stays untouched — as_deref only borrows.

There’s also as_deref_mut for the &mut version, and Result::as_deref / as_deref_mut for the same trick on Result:

1
2
3
let r: Result<String, ()> = Ok("ok".to_string());
let borrowed: Result<&str, &()> = r.as_deref();
assert_eq!(borrowed, Ok("ok"));

Takeaway

Whenever you catch yourself typing .as_ref().map(|x| x.as_str()) or .as_ref().map(|x| &**x), reach for .as_deref(). One method, no closure, and it works on anything that derefs.

148. Result::is_ok_and — Test the Variant and the Value in One Call

Checking “is this Ok, and is the inner value positive?” usually means a match or an if let. Result::is_ok_and collapses both questions into one line.

The pattern crops up everywhere: you have a Result, and you want a bool that says “yes, it succeeded and the value passes some test”. Without help, you write this:

1
2
3
4
5
6
7
let parsed: Result<i32, &str> = "42".parse().map_err(|_| "bad input");

let big_enough = match &parsed {
    Ok(n) => *n > 10,
    Err(_) => false,
};
assert!(big_enough);

It works, but five lines for what reads as one question is a lot. Result::is_ok_and takes a closure that runs only on the Ok value, and returns false for any Err:

1
2
let parsed: Result<i32, &str> = "42".parse().map_err(|_| "bad input");
assert!(parsed.is_ok_and(|n| n > 10));

The closure receives the value by move, so it works cleanly with references too:

1
2
let res: Result<String, ()> = Ok(String::from("rustbites"));
assert!(res.as_ref().is_ok_and(|s| s.starts_with("rust")));

There’s a mirror method for the failure path. Result::is_err_and runs the predicate only on the Err value:

1
2
3
4
5
let res: Result<i32, &str> = Err("missing field: name");
assert!(res.is_err_and(|e| e.contains("name")));

let ok: Result<i32, &str> = Ok(1);
assert!(!ok.is_err_and(|_| true));  // Ok short-circuits to false

Both methods short-circuit on the wrong variant without ever calling the closure, so you can put expensive checks in the predicate without worrying about wasted work on the unhappy path.

A nice place this shines is filtering an iterator of Results:

1
2
3
4
5
6
let inputs = ["12", "hello", "7", "0", "99"];
let count = inputs
    .iter()
    .filter(|s| s.parse::<i32>().is_ok_and(|n| n > 5))
    .count();
assert_eq!(count, 3);  // "12", "7", and "99"

Same trick exists on Option as is_some_and and is_none_or — small surface area, big readability win.