List.filter — Select Elements by Predicate
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
List.filter pred xs maps directly to Rust's Iterator::filter(|x| pred(x))Clone bound when converting from a borrowed &[T] slice to an owned Vec<T> outputFn trait objects in Rust, and why the borrow in Fn(&T) -> bool appears[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
'a values directly; Rust predicates receive &T references because the slice items are borrowed, not owned..cloned() to produce owned values from a borrowed slice.List.filter immediately allocates and returns the result list; Rust's Iterator::filter is lazy — no work is done until .collect() is called.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"]);
}
}#[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
| Concept | OCaml | Rust |
|---|---|---|
| filter | val filter : ('a -> bool) -> 'a list -> 'a list | fn filter<T: Clone, F: Fn(&T) -> bool>(items: &[T], pred: F) -> Vec<T> |
| predicate type | 'a -> bool | Fn(&T) -> bool |
| input | 'a list | &[T] (borrowed slice) |
| output | 'a list | Vec<T> (owned) |
Key Insights
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.&[T] slice is borrowed, so producing an owned Vec<T> requires cloning each element with .cloned() or .copied() for Copy types.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.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.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.