271: Transform-and-Find with find_map()
Functional Programming
Tutorial
The Problem
A very common pattern is: try to convert each element into a useful form, stop at the first success, and return it. For example, parse numbers from strings until one succeeds, look up each key in several registries until a hit, or try multiple fallback strategies until one works. The naive approach chains map() and find(), but this creates intermediate Option values. The find_map(f) adapter fuses these into a single lazy operation: find the first Some(...) result from applying f.
🎯 Learning Outcomes
find_map(f) as fused map(f).flatten().next() — find the first Some from ffind_map optimally handlesfind_map() to parse heterogeneous data, looking for the first valid interpretationfilter_map(): find_map stops at first success, filter_map collects allCode Example
#![allow(clippy::all)]
//! 271. Transform-and-find with find_map()
//!
//! `find_map(f)` finds the first `Some(...)` result — single pass, lazy.
#[cfg(test)]
mod tests {
#[test]
fn test_find_map_parse() {
let strings = ["foo", "bar", "42", "baz"];
let result = strings.iter().find_map(|s| s.parse::<i32>().ok());
assert_eq!(result, Some(42));
}
#[test]
fn test_find_map_none() {
let strings = ["foo", "bar"];
let result = strings.iter().find_map(|s| s.parse::<i32>().ok());
assert_eq!(result, None);
}
#[test]
fn test_find_map_first_match() {
let nums = [1i32, 2, 3, 4, 5];
let result = nums
.iter()
.find_map(|&x| if x > 3 { Some(x * 10) } else { None });
assert_eq!(result, Some(40));
}
}Key Differences
find_map as a standard function with identical semantics.Some — crucial for expensive operations like network lookups or file parsing.find_map returns Option<B> (first success); filter_map returns Iterator<Item=B> (all successes).OCaml Approach
OCaml's List.find_map (standard since OCaml 4.10) is exactly equivalent:
let result = List.find_map (fun s ->
match int_of_string_opt s with
| Some n -> Some n
| None -> None
) ["foo"; "bar"; "42"; "baz"]
(* Some 42 *)
This is one of the cleanest analogies between the two languages — both provide find_map as a standard library function.
Full Source
#![allow(clippy::all)]
//! 271. Transform-and-find with find_map()
//!
//! `find_map(f)` finds the first `Some(...)` result — single pass, lazy.
#[cfg(test)]
mod tests {
#[test]
fn test_find_map_parse() {
let strings = ["foo", "bar", "42", "baz"];
let result = strings.iter().find_map(|s| s.parse::<i32>().ok());
assert_eq!(result, Some(42));
}
#[test]
fn test_find_map_none() {
let strings = ["foo", "bar"];
let result = strings.iter().find_map(|s| s.parse::<i32>().ok());
assert_eq!(result, None);
}
#[test]
fn test_find_map_first_match() {
let nums = [1i32, 2, 3, 4, 5];
let result = nums
.iter()
.find_map(|&x| if x > 3 { Some(x * 10) } else { None });
assert_eq!(result, Some(40));
}
}
✓ Tests
Rust test suite
#[cfg(test)]
mod tests {
#[test]
fn test_find_map_parse() {
let strings = ["foo", "bar", "42", "baz"];
let result = strings.iter().find_map(|s| s.parse::<i32>().ok());
assert_eq!(result, Some(42));
}
#[test]
fn test_find_map_none() {
let strings = ["foo", "bar"];
let result = strings.iter().find_map(|s| s.parse::<i32>().ok());
assert_eq!(result, None);
}
#[test]
fn test_find_map_first_match() {
let nums = [1i32, 2, 3, 4, 5];
let result = nums
.iter()
.find_map(|&x| if x > 3 { Some(x * 10) } else { None });
assert_eq!(result, Some(40));
}
}
Exercises
find_map() to return the first user that exists in a HashMap lookup.i64, then as f64, then as bool, using find_map over a list of parsing functions.find_map from scratch using only map(), filter(), and next(), then verify it produces identical results.