ExamplesBy LevelBy TopicLearning Paths
910 Intermediate

910-iterator-find-map — Iterator find_map

Functional Programming

Tutorial

The Problem

A common pattern: try a transformation on each element, take the first success, ignore failures. Parsing the first valid integer from a list of strings, finding the first key=value pair in a config, finding the first element longer than a threshold — all follow this pattern. The naive approach uses filter_map(f).next(), but find_map(f) expresses the intent more directly: "find the first element for which f returns Some." OCaml's List.find_map was added in 4.10. Haskell's Data.Maybe.mapMaybe and listToMaybe . mapMaybe f serve the same role. It is the "optional value from the first successful transformation" operation.

🎯 Learning Outcomes

  • • Use .find_map(f) to find the first Some(...) result in one pass
  • • Recognize it as more expressive than .filter_map(f).next()
  • • Apply find_map to parsing, searching, and pattern matching in sequences
  • • Implement the recursive OCaml-style find_map_rec
  • • Distinguish from .find(pred) (predicate, returns element) vs .find_map(f) (transform, returns transformed value)
  • Code Example

    pub fn first_int(strings: &[&str]) -> Option<i32> {
        strings.iter().find_map(|s| s.parse::<i32>().ok())
    }

    Key Differences

  • Standard library presence: find_map was added to OCaml in 4.10; Rust has had it since 1.30. Both are now standard.
  • filter_map + next: Rust filter_map(f).next() is equivalent but less intent-revealing than find_map(f); same tradeoff in OCaml.
  • Short-circuit: Both short-circuit — they stop at the first Some result without evaluating the rest.
  • find vs find_map: Rust .find(pred) returns Option<&T> (the element); .find_map(f) returns Option<U> (the transformed value) — more general.
  • OCaml Approach

    List.find_map: ('a -> 'b option) -> 'a list -> 'b option (since 4.10) is the direct equivalent. Before 4.10: let find_map f xs = match List.filter_map f xs with [] -> None | x :: _ -> Some x (inefficient) or recursive manual implementation. List.find_opt: ('a -> bool) -> 'a list -> 'a option is the simpler case (no transformation). For sequences: Seq.find_map is available in OCaml 5.1+.

    Full Source

    #![allow(clippy::all)]
    //! 271. Transform-and-Find with find_map()
    //!
    //! `find_map(f)` finds the first `Some(...)` result — single pass, lazy.
    //! Equivalent to `filter_map(f).next()` but expresses intent more clearly.
    
    /// Parse the first valid integer from a slice of strings.
    pub fn first_int(strings: &[&str]) -> Option<i32> {
        strings.iter().find_map(|s| s.parse::<i32>().ok())
    }
    
    /// Find the first string longer than `min_len` and return its length.
    pub fn first_long_len(strings: &[&str], min_len: usize) -> Option<usize> {
        strings.iter().find_map(|s| {
            if s.len() > min_len {
                Some(s.len())
            } else {
                None
            }
        })
    }
    
    /// Parse the first `key=value` entry from a slice of config-style strings.
    pub fn first_kv<'a>(entries: &[&'a str]) -> Option<(&'a str, &'a str)> {
        entries.iter().find_map(|s| s.split_once('='))
    }
    
    /// Recursive implementation mirroring OCaml's `List.find_map`.
    pub fn find_map_rec<T, B, F>(list: &[T], f: F) -> Option<B>
    where
        F: Fn(&T) -> Option<B>,
    {
        match list {
            [] => None,
            [head, tail @ ..] => f(head).or_else(|| find_map_rec(tail, f)),
        }
    }
    
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_first_int_found() {
            let strings = ["hello", "42", "world", "17"];
            assert_eq!(first_int(&strings), Some(42));
        }
    
        #[test]
        fn test_first_int_none() {
            let strings = ["hello", "world", "foo"];
            assert_eq!(first_int(&strings), None);
        }
    
        #[test]
        fn test_first_int_empty() {
            assert_eq!(first_int(&[]), None);
        }
    
        #[test]
        fn test_first_long_len() {
            let strings = ["hi", "hello", "world", "rust"];
            assert_eq!(first_long_len(&strings, 4), Some(5));
        }
    
        #[test]
        fn test_first_long_len_none() {
            let strings = ["hi", "yo", "ok"];
            assert_eq!(first_long_len(&strings, 4), None);
        }
    
        #[test]
        fn test_first_kv_found() {
            let entries = ["BAD", "PATH=/usr/bin", "HOME=/root"];
            assert_eq!(first_kv(&entries), Some(("PATH", "/usr/bin")));
        }
    
        #[test]
        fn test_first_kv_none() {
            let entries = ["noequals", "alsonone"];
            assert_eq!(first_kv(&entries), None);
        }
    
        #[test]
        fn test_find_map_rec() {
            let nums = [1i32, 2, 3, 10, 4];
            let result = find_map_rec(&nums, |&n| if n > 5 { Some(n * 2) } else { None });
            assert_eq!(result, Some(20));
        }
    
        #[test]
        fn test_find_map_rec_none() {
            let nums = [1i32, 2, 3];
            let result = find_map_rec(&nums, |&n| if n > 100 { Some(n) } else { None });
            assert_eq!(result, None);
        }
    
        #[test]
        fn test_find_map_vs_filter_map_next() {
            // find_map is equivalent to filter_map(...).next()
            let strings = ["a", "2", "b", "3"];
            let via_find_map = strings.iter().find_map(|s| s.parse::<i32>().ok());
            let via_filter_map = strings.iter().filter_map(|s| s.parse::<i32>().ok()).next();
            assert_eq!(via_find_map, via_filter_map);
            assert_eq!(via_find_map, Some(2));
        }
    }
    ✓ Tests Rust test suite
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_first_int_found() {
            let strings = ["hello", "42", "world", "17"];
            assert_eq!(first_int(&strings), Some(42));
        }
    
        #[test]
        fn test_first_int_none() {
            let strings = ["hello", "world", "foo"];
            assert_eq!(first_int(&strings), None);
        }
    
        #[test]
        fn test_first_int_empty() {
            assert_eq!(first_int(&[]), None);
        }
    
        #[test]
        fn test_first_long_len() {
            let strings = ["hi", "hello", "world", "rust"];
            assert_eq!(first_long_len(&strings, 4), Some(5));
        }
    
        #[test]
        fn test_first_long_len_none() {
            let strings = ["hi", "yo", "ok"];
            assert_eq!(first_long_len(&strings, 4), None);
        }
    
        #[test]
        fn test_first_kv_found() {
            let entries = ["BAD", "PATH=/usr/bin", "HOME=/root"];
            assert_eq!(first_kv(&entries), Some(("PATH", "/usr/bin")));
        }
    
        #[test]
        fn test_first_kv_none() {
            let entries = ["noequals", "alsonone"];
            assert_eq!(first_kv(&entries), None);
        }
    
        #[test]
        fn test_find_map_rec() {
            let nums = [1i32, 2, 3, 10, 4];
            let result = find_map_rec(&nums, |&n| if n > 5 { Some(n * 2) } else { None });
            assert_eq!(result, Some(20));
        }
    
        #[test]
        fn test_find_map_rec_none() {
            let nums = [1i32, 2, 3];
            let result = find_map_rec(&nums, |&n| if n > 100 { Some(n) } else { None });
            assert_eq!(result, None);
        }
    
        #[test]
        fn test_find_map_vs_filter_map_next() {
            // find_map is equivalent to filter_map(...).next()
            let strings = ["a", "2", "b", "3"];
            let via_find_map = strings.iter().find_map(|s| s.parse::<i32>().ok());
            let via_filter_map = strings.iter().filter_map(|s| s.parse::<i32>().ok()).next();
            assert_eq!(via_find_map, via_filter_map);
            assert_eq!(via_find_map, Some(2));
        }
    }

    Deep Comparison

    OCaml vs Rust: Transform-and-Find with find_map()

    Side-by-Side Code

    OCaml

    let find_map f lst =
      let rec aux = function
        | [] -> None
        | x :: xs -> (match f x with Some _ as r -> r | None -> aux xs)
      in aux lst
    
    let () =
      let strings = ["hello"; "42"; "world"; "17"; "foo"] in
      let first_int = find_map int_of_string_opt strings in
      Printf.printf "First int: %s\n"
        (match first_int with Some n -> string_of_int n | None -> "None")
    

    Rust (idiomatic)

    pub fn first_int(strings: &[&str]) -> Option<i32> {
        strings.iter().find_map(|s| s.parse::<i32>().ok())
    }
    

    Rust (functional/recursive — mirrors OCaml)

    pub fn find_map_rec<T, B, F>(list: &[T], f: F) -> Option<B>
    where
        F: Fn(&T) -> Option<B>,
    {
        match list {
            [] => None,
            [head, tail @ ..] => f(head).or_else(|| find_map_rec(tail, f)),
        }
    }
    

    Type Signatures

    ConceptOCamlRust
    find_mapval find_map : ('a -> 'b option) -> 'a list -> 'b optionfn find_map<B, F: Fn(&T) -> Option<B>>(&[T], F) -> Option<B>
    List type'a list&[T] (slice)
    Optional value'a optionOption<B>
    Parse intint_of_string_opt : string -> int optionstr::parse::<i32>().ok()

    Key Insights

  • Built-in vs library: OCaml added List.find_map in 4.10; Rust's Iterator::find_map has been stable since 1.30 — it's the idiomatic standard.
  • **Closure returns Option**: The convention is identical — None means "skip", Some(v) means "stop and return v". Both languages encode early termination purely through the return type.
  • Lazy evaluation: Both implementations are short-circuit — once the first Some is found, remaining elements are never processed. Rust's iterator chain makes this explicit and zero-cost.
  • **find_map vs filter_map().next()**: In Rust, iter.find_map(f) is exactly iter.filter_map(f).next() but communicates intent more directly — you're searching, not building a collection.
  • Recursive style: The OCaml recursive pattern maps cleanly to Rust slice patterns ([head, tail @ ..]) with .or_else() replacing the match on the recursive call, keeping the functional structure intact.
  • When to Use Each Style

    **Use idiomatic Rust (find_map)** when scanning an iterator for the first successfully-transformed element — parsing, config lookup, file extension matching. Use recursive Rust when teaching the OCaml parallel explicitly or when working with custom recursive data structures where the iterator API doesn't apply directly.

    Exercises

  • Implement find_valid_config(sources: &[&str]) -> Option<Config> using find_map to try each source and return the first successfully parsed config.
  • Write first_match_group<'a>(patterns: &[Regex], text: &'a str) -> Option<&'a str> using find_map to return the first regex match.
  • Implement resolve_path(dirs: &[&Path], filename: &str) -> Option<PathBuf> using find_map that searches directories for the file.
  • Open Source Repos