ExamplesBy LevelBy TopicLearning Paths
570 Fundamental

Slice Patterns

Functional Programming

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

  • • How [a, b, c] matches exactly three elements
  • • How [first, rest @ ..] matches head and tail (OCaml-style list decomposition)
  • • How [first, second, .., last] matches first, second, and last elements simultaneously
  • • How slice patterns combine with guards for conditional matching
  • • Where slice patterns replace length checks followed by indexing in recursive algorithms
  • Code 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

  • 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.
  • Fixed-length arrays: Rust [a, b, c] on [i32; 3] is exhaustive by definition; OCaml arrays require bounds checking.
  • Middle elements: Rust [first, .., last] extracts first and last; OCaml requires indexing: arr.(0) and arr.(Array.length arr - 1).
  • Performance: Rust slice patterns compile to offset arithmetic on a pointer; OCaml list patterns dereference linked list nodes.
  • 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");
        }
    }
    ✓ Tests Rust test suite
    #[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

  • Recursive sum: Implement fn sum(s: &[i32]) -> i32 using match s { [] => 0, [x, rest @ ..] => x + sum(rest) } — identical structure to OCaml's recursive list sum.
  • Palindrome check: Write fn is_palindrome(s: &[i32]) -> bool using slice patterns: [] | [_] => true, [first, middle @ .., last] if first == last => is_palindrome(middle), _ => false.
  • CSV row parser: Implement 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.
  • Open Source Repos