pattern matching (tipare) în Rust

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.

Sintaxă
let (a, b) = tuplu;
let Punct { x, y } = p;
match v {
    [prim, .., ultim] => ...,     // slice
    n @ 1..=9 => ...,            // legare cu @
    Some(ref s) => ...,          // ref: împrumut în loc de mutare
}

Ce face pattern matching (tipare)

Un tipar (pattern) seamănă cu felul în care construiești valoarea, dar este folosit invers: o desface. let (x, y) = (3, 4); desface un tuplu, let Punct { x, y } = p; desface o structură, Some(n) scoate valoarea din Option. Tiparele apar în let, match, if let, while let, for și în parametrii funcțiilor și ai closure-urilor.

Elemente utile: _ ignoră o valoare, .. ignoră restul câmpurilor sau al elementelor, | dă alternative, a..=b potrivește un interval, nume @ tipar leagă valoarea de un nume și verifică în același timp tiparul, iar [prim, rest @ ..] desface un slice.

ref într-un tipar înseamnă „împrumută, nu muta”: let Elev { ref nume, .. } = elev; dă o referință la nume, iar elev rămâne întreg. Când potrivești o referință (match &elev), Rust împrumută automat câmpurile, deci ref nu mai este necesar; în ediția 2024, a scrie ref în acest caz este chiar eroare.

Exemple

Exemplul 1: Desfacerea structurilor și a tuplurilor

Parametrul funcției distanta este el însuși un tipar care desface tuplul. În let, .. ignoră câmpul judet.

struct Oras {
    nume: String,
    judet: String,
    coordonate: (f64, f64),
}

fn distanta((x1, y1): (f64, f64), (x2, y2): (f64, f64)) -> f64 {
    ((x2 - x1).powi(2) + (y2 - y1).powi(2)).sqrt()
}

fn main() {
    let iasi = Oras { nume: "Iași".into(), judet: "IS".into(), coordonate: (47.16, 27.59) };
    let Oras { nume, coordonate: (lat, lon), .. } = &iasi;
    println!("{nume} ({}): lat {lat}, lon {lon}", iasi.judet);
    println!("Distanța în grade până la (46.77, 23.60): {:.2}", distanta(iasi.coordonate, (46.77, 23.60)));
}

Rezultat

Iași (IS): lat 47.16, lon 27.59
Distanța în grade până la (46.77, 23.60): 4.01

Exemplul 2: Slice-uri, intervale și @

[prima, .., ultima] potrivește orice slice cu cel puțin două elemente. n @ 90..=100 verifică intervalul și păstrează valoarea în n.

fn rezumat(punctaje: &[u32]) -> String {
    match punctaje {
        [] => "niciun punctaj".to_string(),
        [unic] => format!("un singur punctaj: {unic}"),
        [prima, .., ultima] => format!("{} punctaje, de la {prima} la {ultima}", punctaje.len()),
    }
}

fn nivel(p: u32) -> String {
    match p {
        n @ 90..=100 => format!("{n}: excelent"),
        n @ 50..90 => format!("{n}: trecut"),
        n => format!("{n}: picat"),
    }
}

fn main() {
    println!("{}", rezumat(&[]));
    println!("{}", rezumat(&[72]));
    println!("{}", rezumat(&[45, 80, 95, 60]));
    for p in [95, 72, 30] {
        println!("{}", nivel(p));
    }
}

Rezultat

niciun punctaj
un singur punctaj: 72
4 punctaje, de la 45 la 60
95: excelent
72: trecut
30: picat

Exemplul 3: ref: împrumut în loc de mutare

Fără ref, let Elev { nume, .. } = elev; ar muta textul din elev, iar elev nu ar mai putea fi afișat. Cu ref, nume este doar o referință (&String).

#[derive(Debug)]
struct Elev {
    nume: String,
    clasa: u8,
}

fn main() {
    let elev = Elev { nume: String::from("Ioana"), clasa: 10 };
    let Elev { ref nume, clasa } = elev;
    println!("{nume} este în clasa a {clasa}-a");
    println!("Elevul este încă întreg: {elev:?}");
}

Rezultat

Ioana este în clasa a 10-a
Elevul este încă întreg: Elev { nume: "Ioana", clasa: 10 }

Greșeli frecvente

  • Desfaci o structură care conține un String fără & sau ref și apoi folosești structura: textul a fost mutat (E0382). Potrivește &valoare sau folosește ref.
  • Scrii ref când potrivești deja o referință (match &x { Some(ref s) => ... }). În ediția 2024 este eroare; scrie doar Some(s), iar s va fi referință.
  • Folosești în let un tipar care poate eșua: let Some(x) = optiune; nu se compilează (E0005), pentru că ar putea fi None. Folosește let ... else, if let sau match.

Comenzi înrudite

  • match: Compară o valoare cu o listă de tipare și execută ramura primului care se potrivește. Compilatorul verifică dacă toate cazurile sunt tratate.
  • 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.
  • while let: Repetă un bloc cât timp o valoare se potrivește cu un tipar, de exemplu cât timp pop() mai returnează Some.
  • struct: Definește un tip nou care grupează mai multe valori cu nume (câmpuri), de exemplu un elev cu nume, clasă și medie.
  • tuple (tupluri): Grupează un număr fix de valori, de tipuri diferite, într-o singură valoare. Elementele se citesc cu .0, .1 sau prin desfacere.