Slice Patterns
Tutorial Video
Text description (accessibility)
This video demonstrates the "Slice Patterns" functional Rust example. Difficulty level: Fundamental. Key concepts covered: Functional Programming. Recursive algorithms over lists and arrays naturally decompose into head/tail or first/rest cases — the foundation of functional programming. Key difference from OCaml: 1. **Linked list vs slice**: OCaml `::` works on linked lists (O(1) head/tail); Rust `[first, rest @ ..]` works on contiguous slices — `rest` is a `&[T]`, a slice reference.
Tutorial
The Problem
Recursive algorithms over lists and arrays naturally decompose into head/tail or first/rest cases — the foundation of functional programming. OCaml's match on lists is legendary for enabling clean recursive functions. Rust's slice patterns bring the same capability: [first, rest @ ..] matches a non-empty slice and binds head and tail. This enables recursive-style processing of arrays and slices, pattern-based parsing, and elegant handling of variable-length inputs.
🎯 Learning Outcomes
[a, b, c] matches exactly three elements[first, rest @ ..] matches head and tail (OCaml-style list decomposition)[first, second, .., last] matches first, second, and last elements simultaneouslyCode Example
#![allow(clippy::all)]
//! Slice Patterns
//!
//! Matching arrays and slices with [a, b, c] patterns.
/// Match fixed-size array.
pub fn describe_triple(arr: &[i32; 3]) -> String {
match arr {
[0, 0, 0] => "all zeros".to_string(),
[a, b, c] if a == b && b == c => format!("all same: {}", a),
[a, _, c] if a == c => format!("first equals last: {}", a),
[a, b, c] => format!("different: {}, {}, {}", a, b, c),
}
}
/// Match slice head/tail.
pub fn first_and_rest(slice: &[i32]) -> Option<(i32, &[i32])> {
match slice {
[first, rest @ ..] => Some((*first, rest)),
[] => None,
}
}
/// Match multiple elements.
pub fn first_two_last(slice: &[i32]) -> Option<(i32, i32, i32)> {
match slice {
[first, second, .., last] => Some((*first, *second, *last)),
_ => None,
}
}
/// Match from end.
pub fn last_two(slice: &[i32]) -> Option<(i32, i32)> {
match slice {
[.., a, b] => Some((*a, *b)),
_ => None,
}
}
/// Match specific lengths.
pub fn describe_length(slice: &[i32]) -> &'static str {
match slice {
[] => "empty",
[_] => "single",
[_, _] => "pair",
[_, _, _] => "triple",
_ => "many",
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_triple() {
assert!(describe_triple(&[0, 0, 0]).contains("zeros"));
assert!(describe_triple(&[5, 5, 5]).contains("same"));
}
#[test]
fn test_first_and_rest() {
let (first, rest) = first_and_rest(&[1, 2, 3]).unwrap();
assert_eq!(first, 1);
assert_eq!(rest, &[2, 3]);
}
#[test]
fn test_first_two_last() {
let (a, b, c) = first_two_last(&[1, 2, 3, 4, 5]).unwrap();
assert_eq!((a, b, c), (1, 2, 5));
}
#[test]
fn test_last_two() {
assert_eq!(last_two(&[1, 2, 3]), Some((2, 3)));
}
#[test]
fn test_describe_length() {
assert_eq!(describe_length(&[]), "empty");
assert_eq!(describe_length(&[1]), "single");
assert_eq!(describe_length(&[1, 2, 3, 4]), "many");
}
}Key Differences
:: works on linked lists (O(1) head/tail); Rust [first, rest @ ..] works on contiguous slices — rest is a &[T], a slice reference.[a, b, c] on [i32; 3] is exhaustive by definition; OCaml arrays require bounds checking.[first, .., last] extracts first and last; OCaml requires indexing: arr.(0) and arr.(Array.length arr - 1).OCaml Approach
OCaml list pattern matching is the original inspiration for slice patterns:
let rec first_and_rest = function
| [] -> None
| x :: rest -> Some (x, rest)
let rec sum = function
| [] -> 0
| x :: rest -> x + sum rest
OCaml's linked list :: operator is idiomatic for head/tail; Rust slice patterns achieve the same for arrays.
Full Source
#![allow(clippy::all)]
//! Slice Patterns
//!
//! Matching arrays and slices with [a, b, c] patterns.
/// Match fixed-size array.
pub fn describe_triple(arr: &[i32; 3]) -> String {
match arr {
[0, 0, 0] => "all zeros".to_string(),
[a, b, c] if a == b && b == c => format!("all same: {}", a),
[a, _, c] if a == c => format!("first equals last: {}", a),
[a, b, c] => format!("different: {}, {}, {}", a, b, c),
}
}
/// Match slice head/tail.
pub fn first_and_rest(slice: &[i32]) -> Option<(i32, &[i32])> {
match slice {
[first, rest @ ..] => Some((*first, rest)),
[] => None,
}
}
/// Match multiple elements.
pub fn first_two_last(slice: &[i32]) -> Option<(i32, i32, i32)> {
match slice {
[first, second, .., last] => Some((*first, *second, *last)),
_ => None,
}
}
/// Match from end.
pub fn last_two(slice: &[i32]) -> Option<(i32, i32)> {
match slice {
[.., a, b] => Some((*a, *b)),
_ => None,
}
}
/// Match specific lengths.
pub fn describe_length(slice: &[i32]) -> &'static str {
match slice {
[] => "empty",
[_] => "single",
[_, _] => "pair",
[_, _, _] => "triple",
_ => "many",
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_triple() {
assert!(describe_triple(&[0, 0, 0]).contains("zeros"));
assert!(describe_triple(&[5, 5, 5]).contains("same"));
}
#[test]
fn test_first_and_rest() {
let (first, rest) = first_and_rest(&[1, 2, 3]).unwrap();
assert_eq!(first, 1);
assert_eq!(rest, &[2, 3]);
}
#[test]
fn test_first_two_last() {
let (a, b, c) = first_two_last(&[1, 2, 3, 4, 5]).unwrap();
assert_eq!((a, b, c), (1, 2, 5));
}
#[test]
fn test_last_two() {
assert_eq!(last_two(&[1, 2, 3]), Some((2, 3)));
}
#[test]
fn test_describe_length() {
assert_eq!(describe_length(&[]), "empty");
assert_eq!(describe_length(&[1]), "single");
assert_eq!(describe_length(&[1, 2, 3, 4]), "many");
}
}#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_triple() {
assert!(describe_triple(&[0, 0, 0]).contains("zeros"));
assert!(describe_triple(&[5, 5, 5]).contains("same"));
}
#[test]
fn test_first_and_rest() {
let (first, rest) = first_and_rest(&[1, 2, 3]).unwrap();
assert_eq!(first, 1);
assert_eq!(rest, &[2, 3]);
}
#[test]
fn test_first_two_last() {
let (a, b, c) = first_two_last(&[1, 2, 3, 4, 5]).unwrap();
assert_eq!((a, b, c), (1, 2, 5));
}
#[test]
fn test_last_two() {
assert_eq!(last_two(&[1, 2, 3]), Some((2, 3)));
}
#[test]
fn test_describe_length() {
assert_eq!(describe_length(&[]), "empty");
assert_eq!(describe_length(&[1]), "single");
assert_eq!(describe_length(&[1, 2, 3, 4]), "many");
}
}
Deep Comparison
OCaml vs Rust: pattern slice
See example.rs and example.ml for implementations.
Exercises
fn sum(s: &[i32]) -> i32 using match s { [] => 0, [x, rest @ ..] => x + sum(rest) } — identical structure to OCaml's recursive list sum.fn is_palindrome(s: &[i32]) -> bool using slice patterns: [] | [_] => true, [first, middle @ .., last] if first == last => is_palindrome(middle), _ => false.fn parse_coords(parts: &[&str]) -> Option<(f64, f64, f64)> using [x, y, z] and [x, y, z, ..] patterns to accept exactly three or more fields.