if let și let else în Rust

Structuri de control și pattern matching Rust 1.0 (let else: 1.65, lanțuri if let ... &&: 1.88, ediția 2024)

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.

Sintaxă
if let Tipar = valoare {
    ...                    // doar dacă se potrivește
} else {
    ...
}

let Tipar = valoare else {
    return ...;            // trebuie să iasă din funcție/buclă
};

if let Some(x) = a && x > 10 { ... }   // lanț (ediția 2024)

Ce face if let și let else

if let este un match scurt, cu un singur caz interesant: if let Some(pret) = cauta("pâine") { ... } execută blocul doar dacă rezultatul este Some, iar în bloc pret este valoarea scoasă. Ramura else este opțională.

let ... else (Rust 1.65) face invers: dacă tiparul se potrivește, variabilele rămân disponibile după instrucțiune; dacă nu, se execută blocul else, care trebuie să iasă (return, break, continue sau panică). Este ideal pentru verificări la începutul unei funcții, fără să adâncești codul în acolade.

În ediția 2024 (Rust 1.88), condițiile se pot înlănțui cu &&: if let Some(x) = a && let Ok(n) = x.parse::<u32>() && n > 10 { ... }. Fiecare legătură este disponibilă în condițiile de după ea și în bloc.

Exemple

Exemplul 1: if let cu Option

cauta returnează Option<u32>. Pentru produsele găsite se afișează prețul, pentru celelalte se execută else.

fn cauta(produs: &str) -> Option<u32> {
    match produs {
        "pâine" => Some(5),
        "lapte" => Some(9),
        _ => None,
    }
}

fn main() {
    for produs in ["pâine", "cafea", "lapte"] {
        if let Some(pret) = cauta(produs) {
            println!("{produs}: {pret} lei");
        } else {
            println!("{produs}: nu există în stoc");
        }
    }
}

Rezultat

pâine: 5 lei
cafea: nu există în stoc
lapte: 9 lei

Exemplul 2: let else pentru verificări la început

Fiecare rând este desfăcut în nume și notă. Dacă lipsește : sau nota nu este număr, let else sare la rândul următor cu continue. După verificări, nume și nota sunt variabile obișnuite.

fn main() {
    let randuri = ["Ana: 9", "Mihai 8", "Ioana: zece", "Radu: 7"];
    for rand in randuri {
        let Some((nume, text)) = rand.split_once(':') else {
            println!("rând fără ':' -> {rand}");
            continue;
        };
        let Ok(nota) = text.trim().parse::<u8>() else {
            println!("notă invalidă pentru {nume}");
            continue;
        };
        println!("{nume} are nota {nota}");
    }
}

Rezultat

Ana are nota 9
rând fără ':' -> Mihai 8
notă invalidă pentru Ioana
Radu are nota 7

Exemplul 3: Lanțuri if let … && (ediția 2024)

Condiția are trei părți: valoarea există, se poate transforma în număr și numărul este peste 18. Blocul se execută doar dacă toate sunt adevărate.

use std::collections::BTreeMap;

fn main() {
    let mut date = BTreeMap::new();
    date.insert("Ana", "21");
    date.insert("Mihai", "16");
    date.insert("Ioana", "nu știu");

    for nume in ["Ana", "Mihai", "Ioana", "Radu"] {
        if let Some(text) = date.get(nume)
            && let Ok(varsta) = text.parse::<u32>()
            && varsta >= 18
        {
            println!("{nume} ({varsta} ani) poate vota");
        } else {
            println!("{nume}: nu poate vota sau nu știm");
        }
    }
}

Rezultat

Ana (21 ani) poate vota
Mihai: nu poate vota sau nu știm
Ioana: nu poate vota sau nu știm
Radu: nu poate vota sau nu știm

Greșeli frecvente

  • Blocul else al lui let ... else nu iese din funcție: eroarea `else` clause of `let...else` does not diverge. Trebuie să se termine cu return, break, continue sau panic!.
  • Folosești if let cu multe ramuri else if let pe aceeași valoare. Un match este mai clar și verifică dacă ai tratat toate cazurile.
  • Folosești lanțuri if let ... && într-un proiect cu ediția 2021: nu se compilează. Lanțurile cer edition = "2024" în Cargo.toml.

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.
  • 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.
  • 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.
  • operatorul ?: Pus după un Result sau Option: dă valoarea dacă totul e bine, altfel returnează imediat eroarea (sau None) din funcția curentă.