Pattern-Matching ist eines der ausdruckstärksten Sprach-Features in Rust. Patterns leben nicht nur in match-Armen, sondern auch in let-Bindungen, if let, while let, for-Loops, Funktions-Parametern und let else. Dieser Artikel ist eine systematische Referenz: jede Pattern-Form mit Syntax, Anwendung und Beispiel. Von simplen Literalen über Struct-Destrukturierung bis zu @-Bindings und Guard-Bedingungen. Wer alle Pattern-Formen kennt, schreibt Code, der bestimmte Aufgaben in einer Zeile löst, für die andere Sprachen mehrere Code-Blöcke brauchen.

Hinweis zu &'static str: Einige Beispiele in diesem Katalog geben einen Wert vom Typ &'static str zurück — also eine Referenz auf einen Text, der für die gesamte Programm-Laufzeit gültig ist (klassisch ein String-Literal). Die 'static-Notation ist eine Lifetime und wird im Lifetimes-Kapitel ausführlich erklärt; für die Patterns hier reicht das mentale Bild „ein Verweis auf einen fest im Code stehenden Text".

Literal-Patterns

Konkrete Werte als Pattern:

Rust Literale
fn beschreibe(n: i32) -> &'static str {
    match n {
        0 => "null",
        1 => "eins",
        42 => "die Antwort",
        _ => "anderes",
    }
}

fn main() {
    assert_eq!(beschreibe(42), "die Antwort");
}

Die einfachste Form eines Patterns ist ein konkreter Wert. Im Match-Arm steht statt einer Variable oder Wildcard ein Literal: 0, 1, "hello", true. Der Arm matcht nur, wenn der gematchte Wert exakt diesem Literal entspricht.

Literal-Patterns funktionieren auf allen Typen, die PartialEq implementieren — i32, &str, bool, char, Floats (mit der NaN-Einschränkung), eigene Typen mit PartialEq-Derive. Bei Strings ist die Vergleichs-Operation byte-weise; bei Floats ist es das übliche IEEE-754-Verhalten.

Die Or-Pattern-Form "ja" | "yes" | "y" zeigt, wie du mehrere Literale in einem Arm kombinieren kannst — alle drei führen zum gleichen Ergebnis. Sehr typisch für „natürliche Sprache"-Parsing oder Synonyme.

Rust String- und char-Patterns
fn klassifiziere_typ(s: &str) -> &'static str {
    match s {
        "" => "leer",
        "ja" | "yes" | "y" => "positiv",
        "nein" | "no" | "n" => "negativ",
        _ => "anderes",
    }
}

Range-Patterns

Wertebereiche mit ..= (inklusiv) oder .. (exklusiv, nur in Slice-Patterns):

Rust Ranges
fn klassifiziere_alter(jahre: u32) -> &'static str {
    match jahre {
        0..=12 => "Kind",
        13..=17 => "Teenager",
        18..=64 => "Erwachsen",
        65..=u32::MAX => "Senior",
    }
}

fn main() {
    assert_eq!(klassifiziere_alter(8), "Kind");
    assert_eq!(klassifiziere_alter(35), "Erwachsen");
}

Range-Patterns matchen einen Wertebereich statt eines einzelnen Werts. Die Syntax 0..=12 matcht alle Werte zwischen 0 und 12 (inklusive beider Grenzen). Bei der Klassifikation von Altersgruppen oder Bereichen ist das viel kürzer als manuelle if-else-Ketten.

Wichtig: in match-Patterns wird typischerweise ..= (inklusive Ende) verwendet, weil das ..-Pattern (exklusiv) aktuell nur in Slice-Patterns erlaubt ist. Bei 0..=u32::MAX müsste eigentlich der ganze u32-Wertebereich zu Hause sein — der Compiler sieht das auch so und gibt keine „nicht-exhaustive"-Warnung.

Rust char-Range
fn klassifiziere_zeichen(c: char) -> &'static str {
    match c {
        '0'..='9' => "Ziffer",
        'a'..='z' => "Kleinbuchstabe",
        'A'..='Z' => "Großbuchstabe",
        _ => "Anderes",
    }
}

Struct-Patterns

Destrukturierung von Structs mit benannten Feldern:

Rust Struct-Pattern
struct Punkt { x: f64, y: f64 }

fn beschreibe(p: Punkt) -> String {
    match p {
        Punkt { x: 0.0, y: 0.0 } => "Ursprung".to_string(),
        Punkt { x, y: 0.0 } => format!("Auf x-Achse bei {x}"),
        Punkt { x: 0.0, y } => format!("Auf y-Achse bei {y}"),
        Punkt { x, y } => format!("({x}, {y})"),
    }
}

Selektive Bindings mit ..

Rust Mit Rest
struct Person { name: String, alter: u32, email: String }

fn beschreibe(p: &Person) -> String {
    match p {
        Person { name, .. } => format!("Name: {name}"),
    }
}

Die ..-Notation in Struct-Patterns ist eine Convenience: sie steht für „alle übrigen Felder ignorieren". Bei einem Struct mit zehn Feldern, von denen dich nur eines interessiert, sparst du dir, neun mal _ zu schreiben. Du sagst direkt: „nimm name, der Rest ist egal".

Das ist sowohl ein syntaktischer als auch ein semantischer Vorteil: wenn später neue Felder zum Struct hinzukommen, bleibt das Pattern gültig — .. deckt sie automatisch mit ab. Bei explizit aufgelisteten Feldern müsstest du das Pattern jeweils anpassen.

Umbenennen

Rust Rename
# struct Person { name: String, alter: u32, email: String }
fn extrahieren(p: Person) {
    let Person { name: vorname, alter: jahre, .. } = p;
    println!("{vorname} ({jahre} Jahre)");
}

Das Umbenennen-Pattern feld: lokal bindet ein Struct-Feld an einen lokal anderen Namen. Bei Person { name: vorname, alter: jahre } heißt das: nimm das name-Feld der Person und bind es lokal an die Variable vorname; ebenso alter an jahre.

Anwendungsfälle: wenn die Struct-Feldnamen mit lokalen Variablen kollidieren würden (etwa weil du schon ein name im Scope hast), oder wenn lokal andere Namen sinnvoller sind (etwa weil der Code-Kontext anders ist). Auch bei Cross-Domain-Mappings hilfreich — du destrukturierst eine User-Struct mit den Domain-Namen in lokale Variablen mit Code-Namen.

Tuple-Patterns

Destrukturierung von Tupeln und Tuple-Structs:

Rust Tuple
fn analysiere(paar: (i32, i32)) -> &'static str {
    match paar {
        (0, 0) => "Ursprung",
        (x, 0) if x > 0 => "rechts der Achse",
        (x, 0) if x < 0 => "links der Achse",
        (0, _) => "auf y-Achse",
        _ => "anderswo",
    }
}
Rust Tuple-Struct
struct Coord(f64, f64);

fn beschreibe(c: Coord) -> String {
    match c {
        Coord(x, y) if x == y => format!("Diagonale bei {x}"),
        Coord(x, y) => format!("({x}, {y})"),
    }
}

Slice-Patterns

Pattern-Matching auf Slices — vollständig im Slices-Kapitel behandelt, hier eine Zusammenfassung:

Rust Slice-Patterns
fn beschreibe(s: &[i32]) -> &'static str {
    match s {
        [] => "leer",
        [_] => "ein Element",
        [_, _] => "zwei Elemente",
        [first, .., last] if first == last => "erstes = letztes",
        [first, .., last] => "Mehrelementig",
        _ => "irgendwas",
    }
}

Slice-Patterns sind eine ganze Pattern-Familie für sich — siehe den eigenen Artikel im Slices-Kapitel. Hier nur die Kurzform: [] für leeren Slice, [_] für ein Element, [a, b, c] für genau drei Elemente, [first, .., last] mit Rest-Operator. Mit @ lassen sich Rest-Slices an Namen binden: [head, tail @ ..].

Die Kombination aus Slice-Patterns und Guards ist sehr ausdrucksstark: [first, .., last] if first == last matcht Slices mit mindestens zwei Elementen, deren erstes und letztes Element gleich sind. Solche kompakten Beschreibungen wären in vielen Sprachen umständliche Index-Berechnungen.

@-Bindings (Binding mit Test)

Manchmal willst du gleichzeitig matchen und einen Namen binden:

Rust @-Binding
fn klassifiziere(n: i32) -> String {
    match n {
        klein @ 0..=9 => format!("klein: {klein}"),
        mittel @ 10..=99 => format!("mittel: {mittel}"),
        gross => format!("groß: {gross}"),
    }
}

fn main() {
    assert_eq!(klassifiziere(5), "klein: 5");
    assert_eq!(klassifiziere(50), "mittel: 50");
    assert_eq!(klassifiziere(500), "groß: 500");
}

Das @-Binding (auch „at-Pattern" genannt) kombiniert zwei Pattern-Operationen: es matcht auf eine Bedingung und bindet gleichzeitig den gematchten Wert an einen Namen. Die Syntax name @ pattern matcht das Pattern und bindet den Gesamtwert (nicht ein Sub-Feld) an name.

Im Beispiel matcht klein @ 0..=9 jeden Wert im Range 0-9 und bindet ihn an klein. Damit kannst du im Arm-Body sowohl wissen „es ist im Range" als auch den konkreten Wert weiterverarbeiten.

Ohne @ müsstest du entweder den Wert in einer separaten Bindung halten (let klein = n; match klein { ... }) oder Guards verwenden (match n { x if (0..=9).contains(&x) => format!(...) }). Beides ist verbose. Das @-Binding macht die Intention direkt sichtbar.

@ mit Struct-Patterns

Rust @ auf Struct
struct User { id: u64, name: String }

fn beschreibe(u: User) -> String {
    match u {
        User { id: admin_id @ 1..=10, name } =>
            format!("Admin {admin_id}: {name}"),
        u @ User { .. } => format!("Normal: {}", u.name),
    }
}

Im ersten Arm: id wird auf 1..=10 getestet und an admin_id gebunden. Im zweiten: der gesamte User wird an u gebunden.

ref und ref mut — Bindings als Referenz

Standard-Bindings nehmen den Wert per Move (oder Copy bei Copy-Typen). Mit ref bekommst du eine Referenz:

Rust ref
let owned = String::from("Hi");
match owned {
    ref s => println!("{s}"),       // s: &String, owned bleibt nutzbar
}
println!("{owned}");                // ok — nicht gemoved

Standard-Bindings in Patterns nehmen den Wert per Move (oder Copy, falls der Typ Copy ist). Bei einem String würde ein normales match owned { s => ... } den String konsumieren — owned wäre danach nicht mehr nutzbar.

Mit ref s bekommst du stattdessen eine shared Reference (&String) gebunden. Der Original-Wert wird nicht verbraucht und bleibt nach dem Match nutzbar. Das ist analog zum &-Operator beim Pattern selbst — nur dass ref auf der Binding-Seite des Patterns steht, nicht auf der Wert-Seite.

ref mut

Rust ref mut
let mut v = vec![1, 2, 3];
match v {
    ref mut vec_ref => vec_ref.push(99),
}
assert_eq!(v, vec![1, 2, 3, 99]);

ref mut bindet als mutable Referenz.

In modernem Code: meist nicht nötig

Mit „match ergonomics" (seit Rust 2018) wird ref selten gebraucht. Wenn du match &val { ... } schreibst, werden Bindings automatisch zu Referenzen:

Rust Match ergonomics
let owned = String::from("Hi");
match &owned {
    s => println!("{s}"),       // s ist automatisch &String
}
println!("{owned}");            // ok

Mit den Match Ergonomics (seit Rust 2018) ist der ref-Operator in den meisten Fällen überflüssig geworden. Wenn du match &val { ... } schreibst, werden die Bindings automatisch zu Referenzen — der Compiler erkennt, dass du auf einer Referenz matchst, und passt die Binding-Modi entsprechend an.

Das ist sehr viel angenehmer zu lesen und zu schreiben als der explizite ref-Operator. In modernem Code siehst du ref nur noch in seltenen Spezialfällen: beim Destrukturieren von owned Werten in einem Pattern, wo du nur einzelne Felder borrowen willst (während andere gemoved werden), oder in alter Codebase, die Pre-2018-Stil verwendet.

Guards mit if

Eine Pattern kann mit einem Guard ergänzt werden — einer if-Bedingung, die zusätzlich erfüllt sein muss:

Rust Guards
fn beschreibe(n: i32) -> &'static str {
    match n {
        x if x < 0 => "negativ",
        0 => "null",
        x if x % 2 == 0 => "positiv und gerade",
        x if x % 2 != 0 => "positiv und ungerade",
        _ => unreachable!(),
    }
}

Guards sind die mächtige Erweiterung, mit der du beliebige boolesche Bedingungen ans Pattern anhängen kannst. Die Syntax pattern if condition => arm matcht nur dann, wenn sowohl das Pattern passt als auch die Bedingung wahr ist.

Guards haben Zugriff auf alle im Pattern gebundenen Variablen — im Beispiel kann der x % 2 == 0-Guard auf das vom Pattern gebundene x zugreifen. Damit erweiterst du die Match-Logik um Berechnungen, die mit reinen strukturellen Patterns nicht ausdrückbar wären.

Wichtig: der Exhaustiveness-Checker betrachtet Guards als „opaque" — er weiß nicht, dass if x % 2 == 0 und if x % 2 != 0 zusammen alle Werte abdecken. Daher der unreachable!() am Ende: er sagt dem Compiler „dieser Fall kann nicht eintreten" und beruhigt die Exhaustiveness-Prüfung. Wenn du sicher bist, ist unreachable!() korrekt; wenn nicht, ein Panic-Risiko.

Guards mit Struct-Destrukturierung

Rust Combined
struct Order { kunde: String, betrag_cent: i64 }

fn rabatt(o: &Order) -> u32 {
    match o {
        Order { betrag_cent, .. } if *betrag_cent > 10_000 => 15,
        Order { kunde, .. } if kunde.starts_with("VIP_") => 10,
        _ => 0,
    }
}

Destrukturierung plus Guard für „matche und prüfe noch eine zusätzliche Bedingung".

Or-Patterns mit |

Mehrere Patterns mit derselben Aktion:

Rust Or-Pattern
fn ist_vokal(c: char) -> bool {
    match c {
        'a' | 'e' | 'i' | 'o' | 'u' => true,
        'A' | 'E' | 'I' | 'O' | 'U' => true,
        _ => false,
    }
}

Mit dem |-Operator (gesprochen „or") kannst du mehrere alternative Patterns in einem Arm zusammenfassen, die alle zum gleichen Body führen. 'a' | 'e' | 'i' | 'o' | 'u' matcht jedes der fünf Vokale; alle führen zum gleichen => true.

Or-Patterns funktionieren mit allen Pattern-Formen — Literalen, Ranges, Enum-Varianten, Struct-Patterns, geschachtelt. Voraussetzung ist, dass alle alternativen Patterns dieselben Bindungen (mit denselben Typen) deklarieren — sonst kann der Body nicht eindeutig die gebundenen Variablen verwenden. Bei Status::Ok(n) | Status::Warning(n) ist das erfüllt; bei Status::Ok(n) | Status::Error(s) wäre es Compile-Fehler.

Refutable vs. Infallible Patterns

Es gibt zwei Pattern-Kategorien:

  • Refutable — kann fehlschlagen. Beispiel: Some(x) matcht None nicht.
  • Infallible — matcht garantiert. Beispiel: (a, b) matcht jedes (i32, i32).

Wo welche erlaubt sind:

KontextRefutable erlaubt?
let pattern = value;nur infallible
match value { pattern => ... }ja
if let pattern = value { ... }ja
while let pattern = value { ... }ja
let pattern = value else { return; };ja (refutable)
for pattern in iter { ... }nur infallible
Funktions-Parameternur infallible
Rust Refutable/Infallible
fn main() {
    // let mit refutable: Compile-Fehler
    // let Some(n) = Some(42);

    // match: refutable ok
    match Some(42) {
        Some(n) => println!("{n}"),
        None => println!("nichts"),
    }

    // if let: refutable ok
    if let Some(n) = Some(42) {
        println!("{n}");
    }

    // let mit infallible: ok
    let (a, b) = (1, 2);

    // let else: refutable ok, else muss divergent sein
    // let Some(n) = Some(42) else { return; };
}

Praxis: Patterns im echten Code

Token-Verarbeitung mit Or-Patterns

Rust Tokenizer-Klasse
enum Token { Plus, Minus, Mal, Geteilt, Zahl(i32), Klammer(char), Ende }

fn ist_operator(t: &Token) -> bool {
    matches!(t, Token::Plus | Token::Minus | Token::Mal | Token::Geteilt)
}

fn prioritaet(t: &Token) -> Option<u32> {
    match t {
        Token::Plus | Token::Minus => Some(1),
        Token::Mal | Token::Geteilt => Some(2),
        _ => None,
    }
}

matches! ist syntaktischer Zucker für „match auf bool". Or-Patterns gruppieren ähnliche Varianten.

Range-basierte Klassifikation

Rust Status-Code
pub fn klassifiziere(code: u16) -> &'static str {
    match code {
        100..=199 => "Informational",
        200..=299 => "Success",
        300..=399 => "Redirect",
        400..=499 => "Client Error",
        500..=599 => "Server Error",
        _ => "Unknown",
    }
}

Struct-Destrukturierung in Funktions-Parametern

Rust Parameter-Pattern
struct Punkt { x: f64, y: f64 }

fn distanz(Punkt { x: x1, y: y1 }: Punkt, Punkt { x: x2, y: y2 }: Punkt) -> f64 {
    ((x2 - x1).powi(2) + (y2 - y1).powi(2)).sqrt()
}

fn main() {
    let d = distanz(
        Punkt { x: 0.0, y: 0.0 },
        Punkt { x: 3.0, y: 4.0 },
    );
    assert_eq!(d, 5.0);
}

Destrukturierung direkt im Parameter — kein zusätzlicher let-Schritt.

Guard für komplexe Bedingungen

Rust Guard-Heavy
enum Anfrage {
    Lesen { pfad: String, user: String },
    Schreiben { pfad: String, user: String, daten: Vec<u8> },
}

pub fn ist_authorisiert(a: &Anfrage, admin_pfade: &[&str]) -> bool {
    match a {
        Anfrage::Lesen { pfad, user } if user == "admin" => true,
        Anfrage::Lesen { pfad, .. } if !admin_pfade.contains(&pfad.as_str()) => true,
        Anfrage::Schreiben { user, .. } if user == "admin" => true,
        _ => false,
    }
}

Authentifizierungs-Logik mit Patterns plus Guards.

@-Binding für komplexe Validierung

Rust @-Binding
pub fn klassifiziere_port(port: u16) -> String {
    match port {
        priv_port @ 0..=1023 => format!("privilegiert: {priv_port}"),
        user_port @ 1024..=49151 => format!("user: {user_port}"),
        dyn_port @ 49152.. => format!("dynamisch: {dyn_port}"),
    }
}

@-Binding macht den Wert mit Range-Test gleichzeitig verfügbar.

Nested Pattern mit Struct + Enum

Rust Nested
struct Event { typ: EventTyp, timestamp: u64 }
enum EventTyp { Login(String), Logout, Error(u32) }

pub fn handle(e: &Event) {
    match e {
        Event { typ: EventTyp::Login(user), timestamp } =>
            println!("[{timestamp}] Login: {user}"),
        Event { typ: EventTyp::Error(code), .. } if *code >= 500 =>
            println!("Server-Error: {code}"),
        Event { typ: EventTyp::Error(code), .. } =>
            println!("Client-Error: {code}"),
        Event { typ: EventTyp::Logout, .. } =>
            println!("Logout"),
    }
}

Verschachtelte Patterns sind sehr expressiv — Struct mit innerem Enum, alle Felder destrukturierbar.

Or-Pattern mit Bindings

Rust Or mit Binding
enum Nachricht {
    Hallo(String),
    Tschuess(String),
    Custom { typ: String, text: String },
}

pub fn extrahiere_text(n: &Nachricht) -> &str {
    match n {
        Nachricht::Hallo(t) | Nachricht::Tschuess(t) => t,
        Nachricht::Custom { text, .. } => text,
    }
}

Or-Pattern mit gemeinsamem Bindung — t ist in beiden Armen ein &String.

Match auf Tuple für State-Übergänge

Rust State-Machine
#[derive(Debug, PartialEq)]
enum State { Init, Connecting, Connected, Closed }

#[derive(Debug)]
enum Event { Start, Success, Timeout, Close }

pub fn naechster_state(s: &State, e: &Event) -> State {
    match (s, e) {
        (State::Init, Event::Start) => State::Connecting,
        (State::Connecting, Event::Success) => State::Connected,
        (State::Connecting, Event::Timeout) => State::Init,
        (State::Connected, Event::Close) => State::Closed,
        (s, _) => match s {
            State::Init => State::Init,
            State::Connecting => State::Connecting,
            State::Connected => State::Connected,
            State::Closed => State::Closed,
        },
    }
}

State-Machines mit match (state, event) — der idiomatische Stil in Rust.

Verschachtelte Option-Matches

Rust Option in Option
pub fn finde_email(user: Option<&str>, fallback: Option<&str>) -> &'static str {
    match (user, fallback) {
        (Some(u), _) if u.contains('@') => "primary",
        (_, Some(f)) if f.contains('@') => "fallback",
        _ => "kein gültiger Wert",
    }
}

Tuple-Pattern auf zwei Optionen mit Guards.

Besonderheiten

Patterns funktionieren überall, wo Werte gebunden werden.

let, match, if let, while let, let else, for, Funktions-Parameter — alle akzeptieren Patterns. Sehr viele Code-Stellen können dadurch elegant destrukturieren.

Refutable Patterns sind in let verboten.

let Some(n) = x; ist Compile-Fehler, weil Some(n) None nicht matcht. Lösung: let else mit divergentem Branch, oder match/if let.

Or-Patterns dürfen Bindings haben — aber Typ muss gleich sein.

Some(x) | Other(x) funktioniert, wenn x in beiden den gleichen Typ hat. Sonst Compile-Fehler. Sehr nützlich zum Gruppieren von Enum-Varianten mit gleicher Daten-Form.

@-Binding für „matche und behalte".

klein @ 0..=9 matcht das Range und gibt dir gleichzeitig den Wert. Ohne @ müsstest du den Wert separat extrahieren oder einen Guard nutzen. Eleganter Shortcut.

.. ignoriert „den Rest".

In Struct-Patterns: Person { name, .. } — alle anderen Felder ignoriert. In Tuple-Patterns: (first, ..). In Slice-Patterns: [first, ..]. Sehr verbreitet, wenn nur ein Teil interessiert.

ref wird meist nicht mehr gebraucht.

Mit „match ergonomics" matcht match &val { Some(x) => ... } automatisch x als &T. Früher brauchtest du Some(ref x). Heute selten — nur bei selektiven Borrows in Patterns auf owned Werten.

Guards laufen NACH dem Pattern-Match.

Pattern if condition => ... — erst matcht das Pattern, dann wird die Bedingung geprüft. Wenn die Bedingung false ist, geht's zum nächsten Arm. Die Guard-Bedingung hat Zugriff auf alle gebundenen Variablen aus dem Pattern.

Patterns auf Tuple sind extrem mächtig für State-Machines.

match (state, event) { ... } destrukturiert beide Komponenten gleichzeitig. Standard-Pattern für State-Übergänge — jeder Arm ein konkreter Übergang.

Weiterführende Ressourcen

Externe Quellen

/ Weiter

Zurück zu Enums & Pattern Matching

Zur Übersicht