ExamplesBy LevelBy TopicLearning Paths
1191 Fundamental

List.filter — Select Elements by Predicate

ListsHOF

Tutorial

The Problem

Filter a collection by a boolean predicate, keeping only the elements for which the predicate returns true. List.filter is one of the most frequently used higher-order list functions in OCaml, and its Rust equivalent Iterator::filter is equally central to idiomatic Rust code.

🎯 Learning Outcomes

  • • How OCaml's List.filter pred xs maps directly to Rust's Iterator::filter(|x| pred(x))
  • • Why Rust requires a Clone bound when converting from a borrowed &[T] slice to an owned Vec<T> output
  • • How to pass predicates as generic Fn trait objects in Rust, and why the borrow in Fn(&T) -> bool appears
  • • How slice pattern matching [head, rest @ ..] mirrors OCaml's x :: rest for writing explicit recursive filter
  • 🦀 The Rust Way

    Rust's Iterator::filter is the idiomatic tool: items.iter().filter(|x| pred(x)).cloned().collect(). The iterator produces &&T references (the iterator yields &T and the filter closure receives &&T), so the predicate should take &T. The .cloned() adapter handles the T: Clone requirement to produce an owned Vec<T> from borrowed slice elements. A recursive version using slice patterns ([head, rest @ ..]) closely mirrors OCaml's list pattern matching.

    Code Example

    pub fn filter<T: Clone, F>(items: &[T], pred: F) -> Vec<T>
    where
        F: Fn(&T) -> bool,
    {
        items.iter().filter(|x| pred(x)).cloned().collect()
    }
    
    pub fn filter_evens(numbers: &[i32]) -> Vec<i32> {
        numbers.iter().filter(|&&x| x % 2 == 0).copied().collect()
    }

    Key Differences

  • Predicate argument type: OCaml predicates receive 'a values directly; Rust predicates receive &T references because the slice items are borrowed, not owned.
  • Clone requirement: OCaml's GC handles value copying invisibly when constructing the filtered list; Rust must explicitly .cloned() to produce owned values from a borrowed slice.
  • Lazy vs. eager: OCaml's List.filter immediately allocates and returns the result list; Rust's Iterator::filter is lazy — no work is done until .collect() is called.
  • Recursion: Both languages express the recursive filter naturally — OCaml via function pattern matching, Rust via slice patterns [head, rest @ ..].
  • OCaml Approach

    OCaml's List.filter : ('a -> bool) -> 'a list -> 'a list accepts a first-class function and a list, and returns a new list containing only elements for which the function returns true. The predicate is a normal value — an anonymous function fun x -> x mod 2 = 0 is passed directly without any special wrapping. Because OCaml lists are immutable, the original list is never modified; a fresh list is built by walking the spine. The recursive version shows this explicitly: if pred x then x :: filter_rec pred rest else filter_rec pred rest.

    Full Source

    #![allow(dead_code)]
    //! List.filter — Select Elements by Predicate
    //! See example.ml for OCaml reference
    //!
    //! OCaml's `List.filter pred xs` keeps only elements for which `pred` returns true.
    //! Rust's `Iterator::filter` is the direct equivalent.
    
    /// Idiomatic Rust: filter a slice using an iterator adapter.
    /// Mirrors OCaml: `List.filter pred xs`
    pub fn filter<T: Clone, F>(items: &[T], pred: F) -> Vec<T>
    where
        F: Fn(&T) -> bool,
    {
        items.iter().filter(|x| pred(x)).cloned().collect()
    }
    
    /// Functional/recursive: keep elements matching pred by processing head and tail.
    /// Mirrors OCaml pattern matching on the list spine.
    pub fn filter_recursive<T: Clone, F>(items: &[T], pred: &F) -> Vec<T>
    where
        F: Fn(&T) -> bool,
    {
        match items {
            [] => vec![],
            [head, rest @ ..] => {
                let mut tail = filter_recursive(rest, pred);
                if pred(head) {
                    // Prepend matching element to maintain original order.
                    tail.insert(0, head.clone());
                }
                tail
            }
        }
    }
    
    /// Convenience: keep only even integers from a slice.
    pub fn filter_evens(numbers: &[i32]) -> Vec<i32> {
        numbers.iter().filter(|&&x| x % 2 == 0).copied().collect()
    }
    
    /// Convenience: keep only strings longer than `min_len`.
    pub fn filter_long<'a>(words: &[&'a str], min_len: usize) -> Vec<&'a str> {
        words.iter().filter(|s| s.len() > min_len).copied().collect()
    }
    
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_filter_empty() {
            let empty: &[i32] = &[];
            assert_eq!(filter(empty, |_| true), vec![]);
            assert_eq!(filter(empty, |_| false), vec![]);
        }
    
        #[test]
        fn test_filter_single_match() {
            assert_eq!(filter(&[42_i32], |&x| x > 10), vec![42]);
        }
    
        #[test]
        fn test_filter_single_no_match() {
            assert_eq!(filter(&[3_i32], |&x| x > 10), vec![]);
        }
    
        #[test]
        fn test_filter_evens_from_range() {
            let numbers = [1, 2, 3, 4, 5, 6, 7, 8_i32];
            let evens = filter(&numbers, |&x| x % 2 == 0);
            assert_eq!(evens, vec![2, 4, 6, 8]);
        }
    
        #[test]
        fn test_filter_odds_from_range() {
            let numbers = [1, 2, 3, 4, 5, 6, 7, 8_i32];
            let odds = filter(&numbers, |&x| x % 2 != 0);
            assert_eq!(odds, vec![1, 3, 5, 7]);
        }
    
        #[test]
        fn test_filter_all_match() {
            let data = [1, 2, 3_i32];
            assert_eq!(filter(&data, |&x| x < 100), vec![1, 2, 3]);
        }
    
        #[test]
        fn test_filter_none_match() {
            let data = [10, 20, 30_i32];
            assert_eq!(filter(&data, |&x| x < 5), vec![]);
        }
    
        #[test]
        fn test_filter_recursive_matches_idiomatic() {
            let numbers = [1, 2, 3, 4, 5, 6, 7, 8_i32];
            let pred = |x: &i32| x % 2 == 0;
            assert_eq!(filter(&numbers, pred), filter_recursive(&numbers, &pred));
        }
    
        #[test]
        fn test_filter_evens_helper() {
            let numbers = [1, 2, 3, 4, 5, 6_i32];
            assert_eq!(filter_evens(&numbers), vec![2, 4, 6]);
        }
    
        #[test]
        fn test_filter_long_strings() {
            let words = ["hi", "hello", "hey", "salutation"];
            assert_eq!(filter_long(&words, 3), vec!["hello", "salutation"]);
        }
    }
    ✓ Tests Rust test suite
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_filter_empty() {
            let empty: &[i32] = &[];
            assert_eq!(filter(empty, |_| true), vec![]);
            assert_eq!(filter(empty, |_| false), vec![]);
        }
    
        #[test]
        fn test_filter_single_match() {
            assert_eq!(filter(&[42_i32], |&x| x > 10), vec![42]);
        }
    
        #[test]
        fn test_filter_single_no_match() {
            assert_eq!(filter(&[3_i32], |&x| x > 10), vec![]);
        }
    
        #[test]
        fn test_filter_evens_from_range() {
            let numbers = [1, 2, 3, 4, 5, 6, 7, 8_i32];
            let evens = filter(&numbers, |&x| x % 2 == 0);
            assert_eq!(evens, vec![2, 4, 6, 8]);
        }
    
        #[test]
        fn test_filter_odds_from_range() {
            let numbers = [1, 2, 3, 4, 5, 6, 7, 8_i32];
            let odds = filter(&numbers, |&x| x % 2 != 0);
            assert_eq!(odds, vec![1, 3, 5, 7]);
        }
    
        #[test]
        fn test_filter_all_match() {
            let data = [1, 2, 3_i32];
            assert_eq!(filter(&data, |&x| x < 100), vec![1, 2, 3]);
        }
    
        #[test]
        fn test_filter_none_match() {
            let data = [10, 20, 30_i32];
            assert_eq!(filter(&data, |&x| x < 5), vec![]);
        }
    
        #[test]
        fn test_filter_recursive_matches_idiomatic() {
            let numbers = [1, 2, 3, 4, 5, 6, 7, 8_i32];
            let pred = |x: &i32| x % 2 == 0;
            assert_eq!(filter(&numbers, pred), filter_recursive(&numbers, &pred));
        }
    
        #[test]
        fn test_filter_evens_helper() {
            let numbers = [1, 2, 3, 4, 5, 6_i32];
            assert_eq!(filter_evens(&numbers), vec![2, 4, 6]);
        }
    
        #[test]
        fn test_filter_long_strings() {
            let words = ["hi", "hello", "hey", "salutation"];
            assert_eq!(filter_long(&words, 3), vec!["hello", "salutation"]);
        }
    }

    Deep Comparison

    OCaml vs Rust: List.filter — Select Elements by Predicate

    Side-by-Side Code

    OCaml

    (* Idiomatic: built-in List.filter *)
    let evens = List.filter (fun x -> x mod 2 = 0) [1; 2; 3; 4; 5; 6; 7; 8]
    let odds  = List.filter (fun x -> x mod 2 <> 0) [1; 2; 3; 4; 5; 6; 7; 8]
    
    (* Recursive: pattern match on list spine *)
    let rec filter_rec pred = function
      | [] -> []
      | x :: rest ->
        if pred x then x :: filter_rec pred rest
        else filter_rec pred rest
    

    Rust (idiomatic)

    pub fn filter<T: Clone, F>(items: &[T], pred: F) -> Vec<T>
    where
        F: Fn(&T) -> bool,
    {
        items.iter().filter(|x| pred(x)).cloned().collect()
    }
    
    pub fn filter_evens(numbers: &[i32]) -> Vec<i32> {
        numbers.iter().filter(|&&x| x % 2 == 0).copied().collect()
    }
    

    Rust (functional/recursive)

    pub fn filter_recursive<T: Clone, F>(items: &[T], pred: &F) -> Vec<T>
    where
        F: Fn(&T) -> bool,
    {
        match items {
            [] => vec![],
            [head, rest @ ..] => {
                let mut tail = filter_recursive(rest, pred);
                if pred(head) {
                    tail.insert(0, head.clone());
                }
                tail
            }
        }
    }
    

    Type Signatures

    ConceptOCamlRust
    filterval filter : ('a -> bool) -> 'a list -> 'a listfn filter<T: Clone, F: Fn(&T) -> bool>(items: &[T], pred: F) -> Vec<T>
    predicate type'a -> boolFn(&T) -> bool
    input'a list&[T] (borrowed slice)
    output'a listVec<T> (owned)

    Key Insights

  • Direct structural equivalent: List.filter in OCaml and Iterator::filter in Rust implement the same operation — keep elements satisfying a predicate in a single pass. The mental model is identical; only the syntax differs.
  • Clone requirement arises from ownership: OCaml's GC-managed lists share structure freely; the original list is never touched. Rust's &[T] slice is borrowed, so producing an owned Vec<T> requires cloning each element with .cloned() or .copied() for Copy types.
  • Predicate receives a reference: Because items.iter() yields &T, the filter closure receives &&T. The double-deref is handled by writing |&&x| (for Copy types) or |x| pred(x) in a wrapper. OCaml predicates receive the value directly.
  • Lazy evaluation in Rust: Rust's iterator chain is lazy — filter does not execute until a consuming adapter like .collect() is called. This means you can chain .filter().map().take() without intermediate allocations; OCaml's List.filter allocates immediately.
  • Recursive pattern parity: OCaml's x :: rest list destructuring maps cleanly to Rust's [head, rest @ ..] slice pattern. The logic of the recursive version is identical in both languages.
  • When to Use Each Style

    Use idiomatic Rust when: Processing a slice or iterator and collecting the results — .iter().filter(pred).cloned().collect() is the idiomatic one-liner and compiles to a tight loop. Use recursive Rust when: Demonstrating the OCaml parallel explicitly, or when you need to transform the structure during filtering (e.g., wrapping in a tree node) rather than just selecting elements.

    Open Source Repos