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.

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");
}

Literal-Patterns funktionieren auf allen Typen mit PartialEq: i32, &str, bool, char und mehr.

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 funktionieren auf Integer-Typen und auf char:

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}"),
    }
}

.. ignoriert die restlichen Felder. Sehr nützlich, wenn dich nur eines interessiert.

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)");
}

name: vorname bindet das name-Feld an die lokale Variable vorname. Praktisch bei Konflikten oder klareren lokalen 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",
    }
}

[a, .., b], [a, rest @ ..], [a, b, c] — siehe Slice-Patterns-Artikel im Slices-Kapitel.

@-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");
}

klein @ 0..=9 matcht 0..=9 und bindet den Wert an klein. Ohne @ müsstest du match n { x if (0..=9).contains(&x) => format!(...) } schreiben — viel verbose.

@ 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

ref s matcht den Wert und bindet s als shared reference, ohne den Original-Wert zu verbrauchen.

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

ref wird heute nur noch in seltenen Sonderfällen explizit gebraucht (z. B. beim Destrukturieren von owned Werten in einem Pattern, wo du selektiv borrowen willst).

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 beliebige Bool-Expressions. Sie haben Zugriff auf alle im Pattern gebundenen Variablen.

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,
    }
}

Or-Patterns funktionieren mit Literalen, Ranges, Enum-Varianten, Struct-Patterns — überall.

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