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
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
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:
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:
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
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:
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
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:
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:
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
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:
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
fn main() {
let daten: &[i32] = &[1, 2, 3, 4, 5];
if let [first, .., last] = daten {
println!("first={first}, last={last}");
}
}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, 5Sehr funktional — abarbeiten Element für Element ohne Index, mit rest @ .. als Restslice.
Praxis: Slice-Patterns im echten Code
IP-Adresse klassifizieren
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
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
// 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
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
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
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
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
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
- Rust Reference – Slice patterns
- The Rust Book – Patterns
- Rust by Example – Slice patterns
- Rust Reference – Refutability