114-slice-patterns — Slice Patterns
Tutorial
The Problem
Pattern matching on sequences is the heart of functional programming. OCaml's list patterns x :: rest enable recursive algorithms that read naturally. Rust provides analogous slice patterns — [first, rest @ ..], [a, b], [head, .., tail] — for contiguous memory. Unlike OCaml's linked lists, Rust's slice patterns operate on contiguous arrays with O(1) indexed access.
Slice patterns enable writing recursive algorithms in a functional style that the compiler verifies for exhaustiveness, bringing OCaml-style list processing to Rust's arrays and slices.
🎯 Learning Outcomes
[head, rest @ ..] to deconstruct a slice like OCaml's x :: rest[a, b], [a, b, c][first, .., last] to bind first and last without naming the middleCode Example
// Iterator-based — idiomatic for contiguous memory
pub fn sum_idiomatic(slice: &[i32]) -> i32 {
slice.iter().sum()
}
pub fn is_sorted_asc_idiomatic(slice: &[i32]) -> bool {
slice.windows(2).all(|w| w[0] <= w[1])
}Key Differences
[head, rest @ ..] binds a contiguous subslice (a reference, zero cost); OCaml's x :: rest binds the next node in a linked list.rest @ .. is a slice reference — no copying; OCaml's rest in x :: rest is the tail list (shared immutable structure).[| a; b; c |] matches OCaml arrays; Rust's [a, b, c] matches Rust slices of length 3 — semantically equivalent.OCaml Approach
OCaml's list patterns are the original inspiration:
let rec sum = function
| [] -> 0
| x :: rest -> x + sum rest
let describe = function
| [] -> "empty"
| [_] -> "singleton"
| [_; _] -> "pair"
| _ -> "many"
OCaml's list patterns operate on linked lists; Rust's slice patterns operate on contiguous memory. OCaml 4.12+ also supports array patterns with [| a; b |] syntax.
Full Source
#![allow(clippy::all)]
// Example 114: Slice Patterns
//
// Rust can pattern match on slices: [first, rest @ ..], [a, b], [head, .., tail]
// Similar to OCaml's list patterns but on contiguous memory with exhaustiveness checking.
// --- Approach 1: Recursive sum and take using head/tail patterns ---
/// Sum all elements recursively, mirroring OCaml's `| x :: rest -> x + sum rest`.
pub fn sum(slice: &[i32]) -> i32 {
match slice {
[] => 0,
[x, rest @ ..] => x + sum(rest),
}
}
/// Take the first `n` elements, returning a new Vec.
pub fn take(n: usize, slice: &[i32]) -> Vec<i32> {
match (n, slice) {
(0, _) | (_, []) => vec![],
(_, [x, rest @ ..]) => {
let mut result = vec![*x];
result.extend(take(n - 1, rest));
result
}
}
}
// --- Approach 2: Structural shape matching ---
/// Describe the shape of a slice.
pub fn describe(slice: &[i32]) -> &'static str {
match slice {
[] => "empty",
[_] => "singleton",
[_, _] => "pair",
_ => "many",
}
}
/// Return the first and last elements, if they exist.
pub fn first_and_last(slice: &[i32]) -> Option<(i32, i32)> {
match slice {
[] => None,
[only] => Some((*only, *only)),
[head, .., tail] => Some((*head, *tail)),
}
}
// --- Approach 3: Idiomatic iterator-based alternatives ---
/// Idiomatic Rust: sum with `.sum()`.
pub fn sum_idiomatic(slice: &[i32]) -> i32 {
slice.iter().sum()
}
/// Idiomatic Rust: take with `.take()`.
pub fn take_idiomatic(n: usize, slice: &[i32]) -> Vec<i32> {
slice.iter().copied().take(n).collect()
}
/// Detect if a slice is sorted ascending — recursive with slice patterns.
/// Matches [a, b, ..] then recurses on the tail slice.
pub fn is_sorted_asc(slice: &[i32]) -> bool {
match slice {
[] | [_] => true,
[a, b, ..] => a <= b && is_sorted_asc(&slice[1..]),
}
}
/// Idiomatic alternative: sorted check with `.windows(2)`.
pub fn is_sorted_asc_idiomatic(slice: &[i32]) -> bool {
slice.windows(2).all(|w| w[0] <= w[1])
}
#[cfg(test)]
mod tests {
use super::*;
// --- sum ---
#[test]
fn test_sum_empty() {
assert_eq!(sum(&[]), 0);
}
#[test]
fn test_sum_single() {
assert_eq!(sum(&[42]), 42);
}
#[test]
fn test_sum_multiple() {
assert_eq!(sum(&[1, 2, 3, 4, 5]), 15);
}
#[test]
fn test_sum_matches_idiomatic() {
let data = [10, 20, 30];
assert_eq!(sum(&data), sum_idiomatic(&data));
}
// --- take ---
#[test]
fn test_take_zero() {
assert_eq!(take(0, &[1, 2, 3]), vec![]);
}
#[test]
fn test_take_fewer_than_available() {
assert_eq!(take(3, &[1, 2, 3, 4, 5]), vec![1, 2, 3]);
}
#[test]
fn test_take_more_than_available() {
assert_eq!(take(10, &[1, 2]), vec![1, 2]);
}
#[test]
fn test_take_matches_idiomatic() {
let data = [1, 2, 3, 4, 5];
assert_eq!(take(3, &data), take_idiomatic(3, &data));
}
// --- describe ---
#[test]
fn test_describe_empty() {
assert_eq!(describe(&[]), "empty");
}
#[test]
fn test_describe_singleton() {
assert_eq!(describe(&[99]), "singleton");
}
#[test]
fn test_describe_pair() {
assert_eq!(describe(&[1, 2]), "pair");
}
#[test]
fn test_describe_many() {
assert_eq!(describe(&[1, 2, 3]), "many");
assert_eq!(describe(&[1, 2, 3, 4, 5]), "many");
}
// --- first_and_last ---
#[test]
fn test_first_and_last_empty() {
assert_eq!(first_and_last(&[]), None);
}
#[test]
fn test_first_and_last_single() {
assert_eq!(first_and_last(&[7]), Some((7, 7)));
}
#[test]
fn test_first_and_last_multiple() {
assert_eq!(first_and_last(&[1, 2, 3, 4, 5]), Some((1, 5)));
}
#[test]
fn test_first_and_last_pair() {
assert_eq!(first_and_last(&[3, 9]), Some((3, 9)));
}
// --- is_sorted_asc ---
#[test]
fn test_sorted_empty() {
assert!(is_sorted_asc(&[]));
assert!(is_sorted_asc_idiomatic(&[]));
}
#[test]
fn test_sorted_ascending() {
assert!(is_sorted_asc(&[1, 2, 3, 4]));
assert!(is_sorted_asc_idiomatic(&[1, 2, 3, 4]));
}
#[test]
fn test_not_sorted() {
assert!(!is_sorted_asc(&[1, 3, 2, 4]));
assert!(!is_sorted_asc_idiomatic(&[1, 3, 2, 4]));
}
#[test]
fn test_sorted_equal_elements() {
assert!(is_sorted_asc(&[5, 5, 5]));
assert!(is_sorted_asc_idiomatic(&[5, 5, 5]));
}
}#[cfg(test)]
mod tests {
use super::*;
// --- sum ---
#[test]
fn test_sum_empty() {
assert_eq!(sum(&[]), 0);
}
#[test]
fn test_sum_single() {
assert_eq!(sum(&[42]), 42);
}
#[test]
fn test_sum_multiple() {
assert_eq!(sum(&[1, 2, 3, 4, 5]), 15);
}
#[test]
fn test_sum_matches_idiomatic() {
let data = [10, 20, 30];
assert_eq!(sum(&data), sum_idiomatic(&data));
}
// --- take ---
#[test]
fn test_take_zero() {
assert_eq!(take(0, &[1, 2, 3]), vec![]);
}
#[test]
fn test_take_fewer_than_available() {
assert_eq!(take(3, &[1, 2, 3, 4, 5]), vec![1, 2, 3]);
}
#[test]
fn test_take_more_than_available() {
assert_eq!(take(10, &[1, 2]), vec![1, 2]);
}
#[test]
fn test_take_matches_idiomatic() {
let data = [1, 2, 3, 4, 5];
assert_eq!(take(3, &data), take_idiomatic(3, &data));
}
// --- describe ---
#[test]
fn test_describe_empty() {
assert_eq!(describe(&[]), "empty");
}
#[test]
fn test_describe_singleton() {
assert_eq!(describe(&[99]), "singleton");
}
#[test]
fn test_describe_pair() {
assert_eq!(describe(&[1, 2]), "pair");
}
#[test]
fn test_describe_many() {
assert_eq!(describe(&[1, 2, 3]), "many");
assert_eq!(describe(&[1, 2, 3, 4, 5]), "many");
}
// --- first_and_last ---
#[test]
fn test_first_and_last_empty() {
assert_eq!(first_and_last(&[]), None);
}
#[test]
fn test_first_and_last_single() {
assert_eq!(first_and_last(&[7]), Some((7, 7)));
}
#[test]
fn test_first_and_last_multiple() {
assert_eq!(first_and_last(&[1, 2, 3, 4, 5]), Some((1, 5)));
}
#[test]
fn test_first_and_last_pair() {
assert_eq!(first_and_last(&[3, 9]), Some((3, 9)));
}
// --- is_sorted_asc ---
#[test]
fn test_sorted_empty() {
assert!(is_sorted_asc(&[]));
assert!(is_sorted_asc_idiomatic(&[]));
}
#[test]
fn test_sorted_ascending() {
assert!(is_sorted_asc(&[1, 2, 3, 4]));
assert!(is_sorted_asc_idiomatic(&[1, 2, 3, 4]));
}
#[test]
fn test_not_sorted() {
assert!(!is_sorted_asc(&[1, 3, 2, 4]));
assert!(!is_sorted_asc_idiomatic(&[1, 3, 2, 4]));
}
#[test]
fn test_sorted_equal_elements() {
assert!(is_sorted_asc(&[5, 5, 5]));
assert!(is_sorted_asc_idiomatic(&[5, 5, 5]));
}
}
Deep Comparison
OCaml vs Rust: Slice Patterns
Side-by-Side Code
OCaml
(* Head/tail destructuring on linked lists *)
let rec sum = function
| [] -> 0
| x :: rest -> x + sum rest
let describe = function
| [] -> "empty"
| [_] -> "singleton"
| [_; _] -> "pair"
| _ :: _ :: _ -> "many"
Rust (idiomatic)
// Iterator-based — idiomatic for contiguous memory
pub fn sum_idiomatic(slice: &[i32]) -> i32 {
slice.iter().sum()
}
pub fn is_sorted_asc_idiomatic(slice: &[i32]) -> bool {
slice.windows(2).all(|w| w[0] <= w[1])
}
Rust (functional/recursive — slice patterns)
// Structural match on slice shape — mirrors OCaml list patterns
pub fn sum(slice: &[i32]) -> i32 {
match slice {
[] => 0,
[x, rest @ ..] => x + sum(rest),
}
}
pub fn first_and_last(slice: &[i32]) -> Option<(i32, i32)> {
match slice {
[] => None,
[only] => Some((*only, *only)),
[head, .., tail] => Some((*head, *tail)),
}
}
Type Signatures
| Concept | OCaml | Rust |
|---|---|---|
| Recursive sum | val sum : int list -> int | fn sum(slice: &[i32]) -> i32 |
| Head/tail pattern | x :: rest | [x, rest @ ..] |
| Empty pattern | [] | [] |
| Singleton pattern | [x] | [x] |
| First and last | List.hd, List.rev \|> List.hd | [head, .., tail] (one match arm) |
| List type | 'a list (linked list) | &[T] (contiguous slice) |
Key Insights
:: patterns work on singly-linked lists with O(1) head access. Rust's slice patterns work on contiguous memory — a fundamentally different layout that enables cache-friendly traversal.rest @ .. binding**: rest @ .. in Rust captures the remainder of the slice as a &[T] — the @ binds a name to the matched segment. OCaml's rest in x :: rest is just a variable bound to the tail list. Both give you "everything after the head", but Rust's tail is a slice reference, not a new allocation.[head, .., tail] has no OCaml equivalent**: Matching on both the first and last element simultaneously requires an explicit list traversal in OCaml. Rust's .. skip pattern does it in one arm with no extra allocation, exploiting random-access into the slice.[] arm is a compile error in both. In Rust this extends to numeric ranges and struct fields — the same principle, broader application..sum(), .windows(2)) compiles to tighter loops and should be preferred when the standard library already has the operation.When to Use Each Style
Use idiomatic Rust (iterators) when: the operation maps cleanly to .sum(), .take(), .windows(), .zip() or other iterator adapters — these compile to tight loops with no recursion overhead.
Use recursive slice patterns when: the algorithm is naturally recursive (e.g., merge sort, parsing, tree-shaped data embedded in slices), or when you want to make the OCaml parallel explicit for teaching purposes.
Exercises
merge_sorted(a: &[i32], b: &[i32]) -> Vec<i32> using slice patterns to implement the merge step of merge sort.run_length_encode(s: &[i32]) -> Vec<(i32, usize)> using slice patterns to detect runs of equal elements.[Num(n), Plus, Num(m)].