List Filter — Select Elements by Predicate
Tutorial
The Problem
Given a list of integers, keep only those elements satisfying a boolean predicate. Demonstrates List.filter as a higher-order function that decouples the iteration strategy from the selection logic.
🎯 Learning Outcomes
List.filter maps to Rust's Iterator::filter combinatorVec<&T>) vs owned values (Vec<T>) in Rustfn go with &dyn Fn)🦀 The Rust Way
Rust's iterator chain list.iter().filter(|x| predicate(x)).collect() mirrors OCaml semantically. The key decision is whether to return Vec<&T> (borrowed, zero-copy) or Vec<T> (owned, requires Clone). The idiomatic choice depends on the caller's ownership needs. For the recursive variant, a nested fn go with &dyn Fn avoids the infinite monomorphization that arises from passing &predicate recursively with a generic F.
Code Example
pub fn filter<T, F>(list: &[T], predicate: F) -> Vec<&T>
where
F: Fn(&T) -> bool,
{
list.iter().filter(|x| predicate(x)).collect()
}Key Differences
Vec (contiguous memory).Vec<&T> (borrowed refs) from Vec<T> (owned copies) — OCaml has no such split.fn go(&dyn Fn) to recurse without type explosion; OCaml recurses freely.'a -> bool; Rust uses Fn(&T) -> bool, explicitly borrowed.OCaml Approach
OCaml's List.filter pred lst is a standard library function implemented by structural recursion. The predicate is a plain closure fun x -> x mod 2 = 0. The result is a new list — OCaml lists are immutable and singly-linked, so this allocates fresh cons cells.
Full Source
#![allow(dead_code)]
/// Idiomatic Rust: filter a slice using a predicate, returning borrowed refs.
/// Takes &[T] to borrow without allocation; predicate is a closure or fn.
pub fn filter<T, F>(list: &[T], predicate: F) -> Vec<&T>
where
F: Fn(&T) -> bool,
{
list.iter().filter(|x| predicate(x)).collect()
}
/// Filter and clone elements (owned output), mirroring OCaml's List.filter.
pub fn filter_cloned<T: Clone, F>(list: &[T], predicate: F) -> Vec<T>
where
F: Fn(&T) -> bool,
{
list.iter().filter(|x| predicate(x)).cloned().collect()
}
/// Functional/recursive — mirrors the OCaml recursive pattern explicitly.
/// Uses an inner fn with `&dyn Fn` to avoid infinite type instantiation.
pub fn filter_recursive<T: Clone, F>(list: &[T], predicate: F) -> Vec<T>
where
F: Fn(&T) -> bool,
{
fn go<T: Clone>(list: &[T], predicate: &dyn Fn(&T) -> bool) -> Vec<T> {
match list {
[] => vec![],
[head, tail @ ..] => {
let mut rest = go(tail, predicate);
if predicate(head) {
let mut result = vec![head.clone()];
result.append(&mut rest);
result
} else {
rest
}
}
}
}
go(list, &predicate)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_filter_empty() {
let empty: &[i32] = &[];
assert_eq!(filter(empty, |x| x % 2 == 0), Vec::<&i32>::new());
}
#[test]
fn test_filter_evens() {
let numbers = [1, 2, 3, 4, 5, 6, 7, 8];
let evens: Vec<&i32> = filter(&numbers, |x| x % 2 == 0);
assert_eq!(evens, [&2, &4, &6, &8]);
}
#[test]
fn test_filter_odds() {
let numbers = [1, 2, 3, 4, 5, 6, 7, 8];
let odds: Vec<&i32> = filter(&numbers, |x| x % 2 != 0);
assert_eq!(odds, [&1, &3, &5, &7]);
}
#[test]
fn test_filter_none_match() {
let numbers = [1, 3, 5, 7];
assert_eq!(filter(&numbers, |x| x % 2 == 0), Vec::<&i32>::new());
}
#[test]
fn test_filter_all_match() {
let numbers = [2, 4, 6];
let result = filter(&numbers, |x| x % 2 == 0);
assert_eq!(result, [&2, &4, &6]);
}
#[test]
fn test_filter_cloned_evens() {
let numbers = [1, 2, 3, 4, 5, 6, 7, 8];
let evens = filter_cloned(&numbers, |x| x % 2 == 0);
assert_eq!(evens, [2, 4, 6, 8]);
}
#[test]
fn test_filter_recursive_evens() {
let numbers = [1, 2, 3, 4, 5, 6, 7, 8];
let evens = filter_recursive(&numbers, |x| x % 2 == 0);
assert_eq!(evens, [2, 4, 6, 8]);
}
#[test]
fn test_filter_recursive_empty() {
let empty: &[i32] = &[];
assert_eq!(filter_recursive(empty, |x| x % 2 == 0), Vec::<i32>::new());
}
#[test]
fn test_filter_strings() {
let words = ["apple", "banana", "cherry", "date"];
let long_words: Vec<&&str> = filter(&words, |w| w.len() > 5);
assert_eq!(long_words, [&"banana", &"cherry"]);
}
#[test]
fn test_filter_single_element_match() {
let numbers = [42];
assert_eq!(filter(&numbers, |x| *x == 42), [&42]);
}
#[test]
fn test_filter_single_element_no_match() {
let numbers = [42];
assert_eq!(filter(&numbers, |x| *x == 0), Vec::<&i32>::new());
}
}#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_filter_empty() {
let empty: &[i32] = &[];
assert_eq!(filter(empty, |x| x % 2 == 0), Vec::<&i32>::new());
}
#[test]
fn test_filter_evens() {
let numbers = [1, 2, 3, 4, 5, 6, 7, 8];
let evens: Vec<&i32> = filter(&numbers, |x| x % 2 == 0);
assert_eq!(evens, [&2, &4, &6, &8]);
}
#[test]
fn test_filter_odds() {
let numbers = [1, 2, 3, 4, 5, 6, 7, 8];
let odds: Vec<&i32> = filter(&numbers, |x| x % 2 != 0);
assert_eq!(odds, [&1, &3, &5, &7]);
}
#[test]
fn test_filter_none_match() {
let numbers = [1, 3, 5, 7];
assert_eq!(filter(&numbers, |x| x % 2 == 0), Vec::<&i32>::new());
}
#[test]
fn test_filter_all_match() {
let numbers = [2, 4, 6];
let result = filter(&numbers, |x| x % 2 == 0);
assert_eq!(result, [&2, &4, &6]);
}
#[test]
fn test_filter_cloned_evens() {
let numbers = [1, 2, 3, 4, 5, 6, 7, 8];
let evens = filter_cloned(&numbers, |x| x % 2 == 0);
assert_eq!(evens, [2, 4, 6, 8]);
}
#[test]
fn test_filter_recursive_evens() {
let numbers = [1, 2, 3, 4, 5, 6, 7, 8];
let evens = filter_recursive(&numbers, |x| x % 2 == 0);
assert_eq!(evens, [2, 4, 6, 8]);
}
#[test]
fn test_filter_recursive_empty() {
let empty: &[i32] = &[];
assert_eq!(filter_recursive(empty, |x| x % 2 == 0), Vec::<i32>::new());
}
#[test]
fn test_filter_strings() {
let words = ["apple", "banana", "cherry", "date"];
let long_words: Vec<&&str> = filter(&words, |w| w.len() > 5);
assert_eq!(long_words, [&"banana", &"cherry"]);
}
#[test]
fn test_filter_single_element_match() {
let numbers = [42];
assert_eq!(filter(&numbers, |x| *x == 42), [&42]);
}
#[test]
fn test_filter_single_element_no_match() {
let numbers = [42];
assert_eq!(filter(&numbers, |x| *x == 0), Vec::<&i32>::new());
}
}
Deep Comparison
OCaml vs Rust: List Filter — Select Elements by Predicate
Side-by-Side Code
OCaml
let numbers = [1; 2; 3; 4; 5; 6; 7; 8]
let evens = List.filter (fun x -> x mod 2 = 0) numbers
let odds = List.filter (fun x -> x mod 2 <> 0) numbers
Rust (idiomatic — borrowed refs)
pub fn filter<T, F>(list: &[T], predicate: F) -> Vec<&T>
where
F: Fn(&T) -> bool,
{
list.iter().filter(|x| predicate(x)).collect()
}
Rust (idiomatic — owned, mirrors OCaml output)
pub fn filter_cloned<T: Clone, F>(list: &[T], predicate: F) -> Vec<T>
where
F: Fn(&T) -> bool,
{
list.iter().filter(|x| predicate(x)).cloned().collect()
}
Rust (functional/recursive)
pub fn filter_recursive<T: Clone, F>(list: &[T], predicate: F) -> Vec<T>
where
F: Fn(&T) -> bool,
{
fn go<T: Clone>(list: &[T], predicate: &dyn Fn(&T) -> bool) -> Vec<T> {
match list {
[] => vec![],
[head, tail @ ..] => {
let mut rest = go(tail, predicate);
if predicate(head) {
let mut result = vec![head.clone()];
result.append(&mut rest);
result
} else {
rest
}
}
}
}
go(list, &predicate)
}
OCaml (recursive)
let rec filter_rec pred = function
| [] -> []
| x :: rest ->
if pred x then x :: filter_rec pred rest
else filter_rec pred rest
Type Signatures
| Concept | OCaml | Rust |
|---|---|---|
| Filter function | val filter : ('a -> bool) -> 'a list -> 'a list | fn filter<T, F>(list: &[T], predicate: F) -> Vec<&T> |
| List type | 'a list | &[T] (slice) or Vec<T> |
| Predicate type | 'a -> bool | Fn(&T) -> bool |
| Owned output | implicit (all values) | requires T: Clone, use .cloned() |
Key Insights
iter().filter().collect() is the idiomatic translation of List.filter** — the combinator chain mirrors OCaml's functional style directly without a visible loop.List.filter always returns a new list of the same type. In Rust you choose: Vec<&T> avoids allocation but ties lifetimes to the input slice; Vec<T> requires Clone but is independent. Neither is "wrong" — the right choice depends on how the caller uses the result.&dyn Fn helper to avoid infinite monomorphization** — if you write filter_recursive(tail, &predicate) where predicate: F is generic, each recursive call instantiates F = &F = &&F = ... and the compiler hits its recursion limit. The fix is a non-generic inner function fn go(..., predicate: &dyn Fn(...)) that uses dynamic dispatch for the predicate.[head, tail @ ..]) mirror OCaml cons patterns** — OCaml's x :: rest destructures a cons cell; Rust's [head, tail @ ..] destructures a slice. Both express "take the head, recurse on the tail" without indexing.filter().collect() builds the Vec in a single pass through the iterator. The recursive version builds it in reverse (appending to the front via vec![head]; result.append(rest)) which is O(n²) due to Vec front-insertion. For production code, always prefer the iterator version.When to Use Each Style
Use idiomatic iterator Rust when: filtering any slice or collection in production code — it's O(n), composable, and the most readable approach.
Use recursive Rust when: demonstrating the structural correspondence to OCaml, teaching pattern matching on slices, or when the problem is inherently recursive and filter is just a component.