operatorul ? în Rust

Closures și operatorul ? Rust 1.13 (pentru Option: 1.22)

Pus după un Result sau Option: dă valoarea dacă totul e bine, altfel returnează imediat eroarea (sau None) din funcția curentă.

Sintaxă
let valoare = expresie_result?;   // Ok(v) -> v; Err(e) -> return Err(e.into())
let valoare = expresie_option?;   // Some(v) -> v; None -> return None

Ce face operatorul ?

? scurtează tratarea erorilor. let n = text.parse::<u32>()?; înseamnă: dacă parse a reușit (Ok(n)), continuă cu n; dacă nu (Err(e)), ieși imediat din funcție cu Err(e). Fără ? ai scrie un match cu două ramuri la fiecare pas.

? poate fi folosit doar într-o funcție care returnează un tip compatibil: Result (inclusiv main declarat -> Result<(), E>) sau Option. Pe un Option, None este returnat mai departe. Nu poți amesteca: ? pe un Option într-o funcție care returnează Result cere întâi .ok_or(...).

La propagare, ? convertește eroarea cu From::from în tipul de eroare al funcției. De aceea o funcție care returnează Result<T, Box<dyn std::error::Error>> poate folosi ? pe erori de tipuri diferite (ParseIntError, io::Error), iar pentru propriul tip de eroare implementezi From.

Exemple

Exemplul 1: Propagarea erorilor de conversie

Fiecare ? poate opri funcția. Pentru „12,abc” al doilea parse eșuează și funcția returnează eroarea, iar main o afișează.

use std::num::ParseIntError;

fn suma_text(text: &str) -> Result<i32, ParseIntError> {
    let mut suma = 0;
    for parte in text.split(',') {
        let n: i32 = parte.trim().parse()?;
        suma += n;
    }
    Ok(suma)
}

fn main() {
    for intrare in ["10, 20, 12", "12,abc", ""] {
        match suma_text(intrare) {
            Ok(s) => println!("„{intrare}” -> {s}"),
            Err(e) => println!("„{intrare}” -> eroare: {e}"),
        }
    }
}

Rezultat

„10, 20, 12” -> 42
„12,abc” -> eroare: invalid digit found in string
„” -> eroare: cannot parse integer from empty string

Exemplul 2: ? cu Option

Funcția returnează Option, deci ? poate fi folosit pe split_once și pe get: dacă oricare dă None, funcția returnează None.

fn domeniu(email: &str) -> Option<&str> {
    let (_, dupa) = email.split_once('@')?;
    let punct = dupa.rfind('.')?;
    dupa.get(punct + 1..).filter(|tld| !tld.is_empty())
}

fn main() {
    for e in ["ana@exemplu.ro", "fara-arond.ro", "dan@localhost", "x@site."] {
        println!("{e}: {:?}", domeniu(e));
    }
}

Rezultat

ana@exemplu.ro: Some("ro")
fara-arond.ro: None
dan@localhost: None
x@site.: None

Exemplul 3: Erori de tipuri diferite cu Box

Funcția poate eșua la citirea fișierului (io::Error) sau la conversie (ParseFloatError). Cu Box<dyn Error>, ? le convertește automat pe amândouă. Fișierul este creat chiar de program, în directorul curent.

use std::error::Error;
use std::fs;

fn total_din_fisier(cale: &str) -> Result<f64, Box<dyn Error>> {
    let continut = fs::read_to_string(cale)?;
    let mut total = 0.0;
    for linie in continut.lines() {
        total += linie.trim().parse::<f64>()?;
    }
    Ok(total)
}

fn main() -> Result<(), Box<dyn Error>> {
    fs::write("preturi.txt", "12.5\n7.25\n30\n")?;
    println!("Total: {} lei", total_din_fisier("preturi.txt")?);

    fs::write("gresit.txt", "12.5\ndoi lei\n")?;
    if let Err(e) = total_din_fisier("gresit.txt") {
        println!("gresit.txt: {e}");
    }
    if let Err(e) = total_din_fisier("lipsa.txt") {
        println!("lipsa.txt: {e}");
    }
    Ok(())
}

Rezultat

Total: 49.75 lei
gresit.txt: invalid float literal
lipsa.txt: No such file or directory (os error 2)

Greșeli frecvente

  • Folosești ? într-o funcție care returnează () (de exemplu un main obișnuit): eroarea E0277 the `?` operator can only be used in a function that returns `Result` or `Option`. Schimbă tipul returnat.
  • Aplici ? pe un Option într-o funcție cu Result. Transformă întâi: valoare.ok_or("lipsește")?.
  • Pui unwrap() peste tot în loc de ?. unwrap oprește programul cu panică la prima eroare; ? lasă apelantul să decidă ce face.

Comenzi înrudite

  • main: Funcția cu care începe orice program Rust: este apelată automat la pornire, iar programul se termină când ea se termină.
  • 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.
  • type (alias de tip și tip asociat): Dă un nume nou unui tip existent (alias), ca să scurtezi tipuri lungi; în trait-uri, declară un tip asociat.
  • match: Compară o valoare cu o listă de tipare și execută ramura primului care se potrivește. Compilatorul verifică dacă toate cazurile sunt tratate.