🎓 A1Learner
🔍

Rust Slice Patterns

A slice pattern matches an array or a slice with square brackets. It tests the length and binds the ends in one step.

The long way: check the length, then index

Without a slice pattern, binding both ends takes a length check and two index expressions:

let nums = [1, 2, 3, 4];
if nums.len() >= 2 {
    println!("first {}, last {}", nums[0], nums[nums.len() - 1]);
} else {
    println!("too short");
}

Match by length

Square brackets match a slice piece by piece. The empty pattern fits only an empty slice. [only] fits exactly one item:

let nums: &[i32] = &[7];
match nums {
    [] => println!("empty"),
    [only] => println!("single {only}"),
    _ => println!("several"),
}

Bind both ends with ..

Two dots inside the brackets skip the middle, however long it is. This pattern fits any length of 2 or more, and binds both ends at once:

let nums = [1, 2, 3, 4];
match &nums[..] {
    [first, .., last] => println!("first {first}, last {last}"),
    _ => println!("too short"),
}

First item, or nothing

[first, ..] tests "not empty" and takes the first item in one step. A short list simply does not match — it never panics:

let nums: &[i32] = &[];
if let [head, ..] = nums {
    println!("head {head}");
} else {
    println!("nothing here");
}

Save the middle as its own slice

Put a name and @ in front of the dots. The skipped middle is saved as a slice of its own:

let nums = [10, 20, 30, 40];
if let [head, middle @ .., tail] = &nums[..] {
    println!("head {head}, middle {middle:?}, tail {tail}");
}

Pull a fixed array apart in a let

An array has a fixed length, known before the program runs. So a plain let can pull it apart. No match is needed:

let [a, b, c] = [5, 6, 7];
println!("{a} {b} {c}");

Example

fn describe(nums: &[i32]) -> String {
    match nums {
        [] => String::from("empty"),
        [only] => format!("single {}", only),
        [first, .., last] => format!("first {} last {}", first, last),
    }
}
fn main() {
    println!("{}", describe(&[]));
    println!("{}", describe(&[7]));
    println!("{}", describe(&[1, 2, 3]));
    let scores = [10, 20, 30, 40];
    if let [head, rest @ ..] = &scores[..] {
        println!("head {} rest {:?}", head, rest);
    }
}

Key points

  • .. may appear only once in a slice pattern.
  • A second one is rejected.
  • Rust cannot tell where the middle would end.
  • A slice has no fixed length.
  • So slice patterns live in match, if let, and while let.
  • In those places, a non-fit has somewhere to go.

In this example, describe sorts a slice by shape. The empty slice fits [], so it gives "empty". The slice [7] fits [only], so it gives "single 7". The slice [1, 2, 3] fits [first, .., last]: first is 1, last is 3, and .. quietly covers the 2 in the middle. Below, &scores[..] is the whole array borrowed as a slice. The if let pattern [head, rest @ ..] binds head to 10 and saves the rest as a new slice, [20, 30, 40]. One pattern did the length test and the splitting together.

Note

Unlike indexing with [0] and [1], a slice pattern never panics on a short list.

A short list simply does not match, and the next arm gets its turn.

Try it Yourself

Edit the code, then press Run.

Running is simulated · Rust Playground

Exercise

Which pattern matches any non-empty slice and binds its first item?