match în Rust

Compară o valoare cu o listă de tipare și execută ramura primului care se potrivește. Compilatorul verifică dacă toate cazurile sunt tratate.

Sintaxă
match valoare {
    tipar1 => expresie,
    tipar2 | tipar3 => expresie,
    x if conditie => expresie,     // gardă
    _ => expresie,                 // orice altceva
}

Ce face match

match ia o valoare și o compară, de sus în jos, cu tiparele din ramuri. Se execută doar prima ramură care se potrivește. Tiparele pot fi constante (3), intervale (1..=4), alternative ('a' | 'e'), variante de enum (Some(x)), tupluri, structuri și _ (orice).

Cel mai important: match trebuie să fie exhaustiv. Dacă există o valoare posibilă pe care nu o tratează nicio ramură, programul nu se compilează (eroarea E0004). Când adaugi o variantă nouă într-un enum, compilatorul îți arată toate locurile unde trebuie tratată.

match este o expresie, deci poate da o valoare, iar toate ramurile trebuie să aibă același tip. O gardă (x if x > 100 =>) adaugă o condiție în plus unei ramuri.

Exemple

Exemplul 1: Numere, intervale și alternative

Fiecare zi a săptămânii este tratată; 6 | 7 potrivește două valori, iar _ prinde orice număr invalid.

fn tip_zi(zi: u8) -> &'static str {
    match zi {
        1..=5 => "zi lucrătoare",
        6 | 7 => "weekend",
        _ => "zi invalidă",
    }
}

fn main() {
    for zi in [1, 5, 6, 7, 9] {
        println!("{zi}: {}", tip_zi(zi));
    }
}

Rezultat

1: zi lucrătoare
5: zi lucrătoare
6: weekend
7: weekend
9: zi invalidă

Exemplul 2: match pe un enum, cu gardă

Fiecare variantă a lui Plata are ramura ei. Garda if suma > 5000 separă plățile mari cu cardul de restul. Valorile din variante (suma, iban) sunt extrase direct în tipar.

enum Plata {
    Numerar(u32),
    Card { suma: u32, banca: String },
    Transfer { suma: u32, iban: String },
}

fn descrie(p: &Plata) -> String {
    match p {
        Plata::Numerar(suma) => format!("{suma} lei numerar"),
        Plata::Card { suma, .. } if *suma > 5000 => format!("{suma} lei cu cardul (cere confirmare)"),
        Plata::Card { suma, banca } => format!("{suma} lei cu cardul {banca}"),
        Plata::Transfer { suma, iban } => format!("{suma} lei prin transfer din {iban}"),
    }
}

fn main() {
    let plati = [
        Plata::Numerar(50),
        Plata::Card { suma: 120, banca: "BT".to_string() },
        Plata::Card { suma: 7200, banca: "BCR".to_string() },
        Plata::Transfer { suma: 900, iban: "RO49AAAA1B31007593840000".to_string() },
    ];
    for p in &plati {
        println!("{}", descrie(p));
    }
}

Rezultat

50 lei numerar
120 lei cu cardul BT
7200 lei cu cardul (cere confirmare)
900 lei prin transfer din RO49AAAA1B31007593840000

Exemplul 3: Un caz uitat nu trece de compilare

Varianta Galben nu are ramură. Compilatorul refuză programul și arată exact ce lipsește.

enum Semafor {
    Rosu,
    Galben,
    Verde,
}

fn actiune(s: Semafor) -> &'static str {
    match s {
        Semafor::Rosu => "oprește",
        Semafor::Verde => "treci",
    }
}

fn main() {
    println!("{}", actiune(Semafor::Verde));
    println!("{}", actiune(Semafor::Rosu));
    println!("{}", actiune(Semafor::Galben));
}

Mesajul compilatorului (programul nu se compilează)

error[E0004]: non-exhaustive patterns: `Semafor::Galben` not covered
  --> main.rs:8:11
   |
 8 |     match s {
   |           ^ pattern `Semafor::Galben` not covered
   |
note: `Semafor` defined here
  --> main.rs:1:6
   |
 1 | enum Semafor {
   |      ^^^^^^^
 2 |     Rosu,
 3 |     Galben,
   |     ------ not covered
   = note: the matched value is of type `Semafor`
help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern or an explicit pattern as shown
   |
10 ~         Semafor::Verde => "treci",
11 ~         Semafor::Galben => todo!(),
   |

error: aborting due to 1 previous error

For more information about this error, try `rustc --explain E0004`.

Greșeli frecvente

  • Pui ramura _ prima: ea potrivește orice, deci ramurile de după ea nu se mai execută niciodată (compilatorul avertizează unreachable pattern).
  • Folosești _ => într-un match pe propriul tău enum. Programul se compilează, dar pierzi avantajul: când adaugi o variantă nouă, compilatorul nu te mai anunță unde trebuie tratată.
  • Crezi că un nume simplu dintr-un tipar compară cu o variabilă: match x { limita => ... } nu compară cu variabila limita, ci creează o variabilă nouă care potrivește orice. Folosește o gardă: v if v == limita =>.

Comenzi înrudite

  • pattern matching (tipare): Tiparele descriu forma unei valori și scot din ea părțile de care ai nevoie: în let, match, if let, for și parametrii funcțiilor.
  • if let și let else: if let execută un bloc doar dacă o valoare se potrivește cu un tipar; let else desface valoarea sau iese imediat din funcție.
  • enum: Definește un tip care poate fi exact una dintre mai multe variante; fiecare variantă poate avea propriile date.
  • matches!: Returnează true dacă o valoare se potrivește cu un tipar (ca într-o ramură de match) și false altfel.
  • if și else: Execută un bloc de cod doar dacă o condiție bool este adevărată. În Rust, if este o expresie: poate da o valoare.