Slice-Patterns sind eine der elegantesten Formen des Rust-Pattern-Matching: du kannst direkt auf den Inhalt und die Länge eines Slices matchen, einzelne Elemente an Namen binden und „Rest"-Bereiche mit .. ausblenden oder festhalten. Damit lassen sich Algorithmen, die in anderen Sprachen mit Index-Arithmetik und Längen-Checks erstickt würden, sehr kompakt schreiben. Dieser Artikel zeigt die volle Syntax — von einfachen Längen-Checks bis zu Rest-Bindings mit @-Operator — und beweist, dass Slice-Patterns nicht nur schön, sondern auch praktisch sind.

Die Grundformen

Exakte Länge

Rust Fixed-Length
fn beschreibe(s: &[i32]) -> &'static str {
    match s {
        [] => "leer",
        [_] => "ein Element",
        [_, _] => "zwei Elemente",
        [_, _, _] => "drei Elemente",
        _ => "mehr als drei",
    }
}

fn main() {
    assert_eq!(beschreibe(&[]), "leer");
    assert_eq!(beschreibe(&[1]), "ein Element");
    assert_eq!(beschreibe(&[1, 2, 3]), "drei Elemente");
    assert_eq!(beschreibe(&[1, 2, 3, 4]), "mehr als drei");
}

Jedes _ matcht genau ein Element. Pattern [_, _, _] matcht alle Slices mit genau drei Elementen.

Elemente binden

Rust Bindings
fn extrahiere_drei(s: &[i32]) -> Option<(i32, i32, i32)> {
    match s {
        [a, b, c] => Some((*a, *b, *c)),
        _ => None,
    }
}

fn main() {
    assert_eq!(extrahiere_drei(&[10, 20, 30]), Some((10, 20, 30)));
    assert_eq!(extrahiere_drei(&[1, 2]), None);
}

a, b, c werden an die jeweiligen Elemente gebunden. Bei &[i32] sind sie &i32-Referenzen — daher das * beim Dereferenzieren.

Der ..-Rest-Operator

Mit .. kannst du beliebig viele Elemente überspringen:

Rust Rest-Pattern
fn first_und_last(s: &[i32]) -> Option<(i32, i32)> {
    match s {
        [first, .., last] => Some((*first, *last)),
        [only] => Some((*only, *only)),
        [] => None,
    }
}

fn main() {
    assert_eq!(first_und_last(&[1, 2, 3, 4]), Some((1, 4)));
    assert_eq!(first_und_last(&[42]), Some((42, 42)));
    assert_eq!(first_und_last(&[]), None);
}

[first, .., last] matcht jeden Slice mit mindestens zwei Elementen: erstes und letztes werden gebunden, dazwischen kann beliebig viel sein.

Rest binden mit @

Wenn du den Rest auch als Slice brauchst, nutzt du den @-Operator:

Rust Rest binden
fn kopf_und_schwanz(s: &[i32]) -> Option<(i32, &[i32])> {
    match s {
        [head, tail @ ..] => Some((*head, tail)),
        [] => None,
    }
}

fn main() {
    assert_eq!(kopf_und_schwanz(&[1, 2, 3]), Some((1, &[2, 3][..])));
    assert_eq!(kopf_und_schwanz(&[]), None);
}

tail @ .. bindet den Rest als &[i32]-Slice. Damit kannst du rekursive Algorithmen auf Slices schreiben — ohne Index-Arithmetik.

Front, Middle, Back

Rust Drei-Wege-Split
fn zerlege(s: &[i32]) -> Option<(i32, &[i32], i32)> {
    match s {
        [first, middle @ .., last] => Some((*first, middle, *last)),
        _ => None,
    }
}

fn main() {
    assert_eq!(zerlege(&[1, 2, 3, 4]), Some((1, &[2, 3][..], 4)));
    assert_eq!(zerlege(&[1, 2]), Some((1, &[][..], 2)));      // middle leer
    assert_eq!(zerlege(&[1]), None);                            // < 2 Elemente
}

[first, middle @ .., last] — erstes Element, mittleres Slice, letztes Element. Bei genau 2 Elementen ist middle ein leerer Slice.

Literale matchen

Du kannst auch konkrete Werte matchen:

Rust Literal-Match
fn ist_palindrom_kurz(s: &[i32]) -> bool {
    match s {
        [] => true,
        [_] => true,
        [a, b] => a == b,
        [a, _, c] => a == c,
        [a, b, c, d] => a == d && b == c,
        _ => false,        // länger: hier nicht behandelt
    }
}

Patterns mit Bindings — und auf den gebundenen Werten kann mit Guards weiter geprüft werden.

Konkrete Werte

Rust Mit Literalen
fn klassifiziere(rgb: &[u8]) -> &'static str {
    match rgb {
        [0, 0, 0] => "schwarz",
        [255, 255, 255] => "weiß",
        [r, 0, 0] if *r > 0 => "rot",
        [0, g, 0] if *g > 0 => "grün",
        [0, 0, b] if *b > 0 => "blau",
        _ => "mischfarbe",
    }
}

fn main() {
    assert_eq!(klassifiziere(&[0, 0, 0]), "schwarz");
    assert_eq!(klassifiziere(&[255, 0, 0]), "rot");
    assert_eq!(klassifiziere(&[100, 50, 200]), "mischfarbe");
}

Konkrete Werte (0, 255) plus Guards (if *r > 0) ermöglichen sehr ausdrucksstarke Patterns.

Slice-Patterns in let

Slice-Patterns funktionieren auch in let-Bindungen und let else:

Rust let Slice-Pattern
fn main() {
    let punkte = [(1, 2), (3, 4), (5, 6)];
    let [erste, zweite, dritte] = punkte;
    println!("{erste:?} {zweite:?} {dritte:?}");
}

let [a, b, c] = ... funktioniert nur bei Arrays mit fester Länge, die zur Compile-Zeit bekannt ist. Bei dynamischen Slices: let else:

Rust let else mit Slice
fn parse_befehl(args: &[String]) -> Result<(&str, &str), &'static str> {
    let [_program, cmd, arg] = args else {
        return Err("Aufruf: programm <cmd> <arg>");
    };
    Ok((cmd, arg))
}

let [_program, cmd, arg] = args else { ... } matcht nur, wenn args exakt 3 Elemente hat. Sonst: divergierender Branch.

Rest in let else

Rust let mit Rest
fn erstes_argument(args: &[String]) -> Option<&str> {
    let [_program, erstes, ..] = args else {
        return None;
    };
    Some(erstes)
}

Sehr saubere CLI-Argument-Validierung.

Slice-Patterns auf String-Bytes

Patterns funktionieren auf jedem Slice-Typ — auch auf Byte-Slices:

Rust Magic-Bytes
fn erkenne_format(bytes: &[u8]) -> &'static str {
    match bytes {
        [0x89, b'P', b'N', b'G', ..] => "PNG",
        [0xFF, 0xD8, 0xFF, ..] => "JPEG",
        [b'G', b'I', b'F', b'8', _, b'a', ..] => "GIF",
        [0x25, 0x50, 0x44, 0x46, ..] => "PDF",
        _ => "unbekannt",
    }
}

fn main() {
    assert_eq!(erkenne_format(&[0x89, b'P', b'N', b'G', 0x00, 0x00]), "PNG");
    assert_eq!(erkenne_format(&[0xFF, 0xD8, 0xFF, 0xE0]), "JPEG");
    assert_eq!(erkenne_format(b"GIF89a"), "GIF");
}

Dateiformat-Erkennung mit Magic-Bytes — extrem kompakt und lesbar. b'P' ist ein Byte-Literal vom Typ u8.

Slice-Patterns in if let und while let

Rust if let
fn main() {
    let daten: &[i32] = &[1, 2, 3, 4, 5];
    if let [first, .., last] = daten {
        println!("first={first}, last={last}");
    }
}
Rust while let — Slice abarbeiten
fn main() {
    let mut daten: &[i32] = &[1, 2, 3, 4, 5];
    while let [erstes, rest @ ..] = daten {
        println!("Bearbeite: {erstes}");
        daten = rest;
    }
}
// Ausgabe: 1, 2, 3, 4, 5

Sehr funktional — abarbeiten Element für Element ohne Index, mit rest @ .. als Restslice.

Praxis: Slice-Patterns im echten Code

IP-Adresse klassifizieren

Rust IPv4-Klassen
pub fn ipv4_klasse(addr: &[u8; 4]) -> &'static str {
    match addr {
        [10, ..] => "private (10.x.x.x)",
        [172, b, ..] if (16..=31).contains(b) => "private (172.16-31.x.x)",
        [192, 168, ..] => "private (192.168.x.x)",
        [127, ..] => "loopback",
        [255, 255, 255, 255] => "broadcast",
        _ => "public",
    }
}

fn main() {
    assert_eq!(ipv4_klasse(&[192, 168, 1, 1]), "private (192.168.x.x)");
    assert_eq!(ipv4_klasse(&[127, 0, 0, 1]), "loopback");
    assert_eq!(ipv4_klasse(&[8, 8, 8, 8]), "public");
}

Slice-Patterns sind perfekt für IP-Klassifikation. Statt Bit-Maskierung kannst du direkt auf die Bytes matchen.

CLI-Argument-Dispatcher

Rust Argv-Pattern
pub fn dispatch(args: &[String]) -> Result<(), String> {
    match args {
        [_, cmd] if cmd == "help" => {
            println!("Verfügbare Befehle: help, status, restart");
            Ok(())
        }
        [_, cmd] if cmd == "status" => {
            println!("OK");
            Ok(())
        }
        [_, cmd, arg] if cmd == "restart" => {
            println!("Restart von {arg}");
            Ok(())
        }
        [_, cmd, ..] => Err(format!("unbekanntes Kommando: {cmd}")),
        _ => Err("kein Kommando angegeben".into()),
    }
}

CLI-Argument-Dispatch ohne externe Dependencies. Slice-Patterns plus Guards machen das natürlich lesbar.

Polynom-Auswertung

Rust Horner-Schema
// Horner-Schema rekursiv mit Slice-Patterns
pub fn polynom_eval(koeffizienten: &[f64], x: f64) -> f64 {
    match koeffizienten {
        [] => 0.0,
        [a] => *a,
        [a, rest @ ..] => a + x * polynom_eval(rest, x),
    }
}

fn main() {
    // 1 + 2*x + 3*x²
    let koef = [1.0, 2.0, 3.0];
    assert_eq!(polynom_eval(&koef, 2.0), 1.0 + 4.0 + 12.0);
}

Rekursive Polynom-Auswertung — Pattern-Matching macht das Basis-Case + Rekursions-Step elegant lesbar.

Versions-String-Parse

Rust Semver
pub fn parse_version(v: &str) -> Option<(u32, u32, u32)> {
    let parts: Vec<&str> = v.split('.').collect();
    match parts.as_slice() {
        [major, minor, patch] => Some((
            major.parse().ok()?,
            minor.parse().ok()?,
            patch.parse().ok()?,
        )),
        _ => None,
    }
}

fn main() {
    assert_eq!(parse_version("1.78.0"), Some((1, 78, 0)));
    assert_eq!(parse_version("1.78"), None);
    assert_eq!(parse_version("1.78.0.0"), None);
}

parts.as_slice() macht aus &Vec<&str> ein &[&str] — dann Slice-Pattern für exakte Längen-Validierung.

Tail-Recursive Listen-Verarbeitung

Rust Rekursive Summe
pub fn summe_rekursiv(slice: &[i32]) -> i32 {
    match slice {
        [] => 0,
        [first, rest @ ..] => *first + summe_rekursiv(rest),
    }
}

fn main() {
    assert_eq!(summe_rekursiv(&[1, 2, 3, 4, 5]), 15);
}

Funktionaler Stil — Basis-Case (leerer Slice) + Rekursions-Case (erstes Element plus Rest). Klassische Funktional-Pattern.

Min-Max-Track durch rekursives Splitting

Rust Min-Max
pub fn min_max(slice: &[i32]) -> Option<(i32, i32)> {
    match slice {
        [] => None,
        [only] => Some((*only, *only)),
        [first, rest @ ..] => {
            let (sub_min, sub_max) = min_max(rest)?;
            Some((first.min(&sub_min).clone(), first.max(&sub_max).clone()))
        }
    }
}

fn main() {
    assert_eq!(min_max(&[3, 1, 4, 1, 5, 9, 2, 6]), Some((1, 9)));
}

Erste-und-Letzte-Element-Validation

Rust Validate Endpoints
pub fn ist_pfad_absolut(parts: &[&str]) -> bool {
    match parts {
        [first, ..] if first.is_empty() => true,    // beginnt mit `/`
        _ => false,
    }
}

fn main() {
    let absolute = "/etc/hosts".split('/').collect::<Vec<_>>();
    let relative = "etc/hosts".split('/').collect::<Vec<_>>();
    assert!(ist_pfad_absolut(&absolute));
    assert!(!ist_pfad_absolut(&relative));
}

Packet-Header-Validierung

Rust Header-Validator
pub fn validiere_paket(bytes: &[u8]) -> Result<(), &'static str> {
    match bytes {
        [] => Err("leeres Paket"),
        [magic, ..] if *magic != 0xAB => Err("falsches Magic-Byte"),
        [_, version, ..] if *version != 1 => Err("unbekannte Version"),
        [_, _, len, .., payload @ ..] if *len as usize != payload.len() => {
            Err("Längen-Feld falsch")
        }
        _ => Ok(()),
    }
}

Multi-Stufen-Validierung in einem match. Jeder Arm prüft eine andere Bedingung.

Besonderheiten

Slice-Patterns prüfen Länge UND Inhalt in einem Schritt.

Anstatt if v.len() == 3 && v[0] == 1 { ... } schreibst du if let [1, _, _] = v.as_slice() { ... }. Kompakter, ohne Index-Out-of-Bounds-Risiko, und Pattern-Matching ist exhaustive geprüft.

.. ist Rest-Pattern, name @ .. bindet ihn.

[a, .., b] ignoriert den Rest. [a, mid @ .., b] bindet ihn als Slice. Wer den Rest weiterverarbeiten will, braucht das @. Wer ihn nur überspringen will, reicht ...

Patterns sind refutable in match, infallibel in normalem let.

let [a, b] = &slice; ist Compile-Fehler bei slice: &[i32] — die Länge ist nicht garantiert. In match, if let, while let, let else darfst du refutable Patterns nutzen. Bei festen Array-Längen (&[i32; 3]) ist auch direktes let ok.

Bei &[T] matchen Bindings auf &T.

match s { [a, b, c] => ... } mit s: &[i32] bindet a, b, c als &i32. Beim Vergleich oder Arithmetik brauchst du oft *a zum Dereferenzieren — außer bei Copy-Typen, wo Auto-Deref greift.

.. matcht null oder mehr Elemente.

[a, ..] matcht jeden Slice mit mindestens 1 Element. [a, b, ..] mindestens 2. [a, .., b] mindestens 2 (das .. darf 0 sein). [..] matcht jeden Slice, inklusive leer.

Slice-Patterns funktionieren auf Vec via as_slice.

match v.as_slice() { ... } mit v: Vec<T> macht Slice-Pattern-Matching auf den Vec-Inhalt möglich. Direkter Match auf Vec ist nicht erlaubt — der Slice-Pattern braucht eine Slice-Referenz.

Slice-Patterns sind exhaustive geprüft.

Bei einem match über &[i32] muss jeder mögliche Slice von einem Arm getroffen werden. Sonst Compile-Fehler. Mit _ als Wildcard-Arm sicher abgedeckt. Die Exhaustiveness-Garantie ist eine der Kern-Stärken von Pattern-Matching.

Guards (if ...) ergänzen Patterns.

Wenn das Pattern allein nicht reicht: Guard hinzufügen. [a, ..] if a.is_positive() => ... matcht nur, wenn das erste Element positiv ist. Sehr nützlich für semantische Bedingungen.

Weiterführende Ressourcen

Externe Quellen

/ Weiter

Zurück zu Slices & Views

Zur Übersicht