todo! și unimplemented! în Rust

Macro-uri de verificare și panică Rust 1.40 (todo!), Rust 1.0 (unimplemented!)

Țin locul unui cod nescris: programul se compilează, dar dacă se ajunge acolo, dă panică cu „not yet implemented”.

Sintaxă
fn nume() -> Tip {
    todo!()                         // „încă nu am scris asta”
}
todo!("mesaj {}", x);
unimplemented!("nu este suportat");

Ce face todo! și unimplemented!

todo!() marchează cod pe care urmează să-l scrii. Tipul lui este ! (tipul care nu returnează niciodată), deci se potrivește oriunde: ca valoare a unei funcții care returnează u32, într-o ramură de match etc. Așa poți compila și testa restul programului înainte să termini totul. Dacă execuția ajunge la el, programul dă panică cu not yet implemented.

unimplemented!() face același lucru tehnic (panică cu not implemented), dar are alt sens: „această funcționalitate nu este și nu va fi suportată aici”, de exemplu o metodă a unui trait care nu are sens pentru un anumit tip.

Ambele primesc opțional un mesaj formatat. Compilatorul nu avertizează că ai todo! în cod; caută-le cu editorul înainte de o versiune finală.

Exemple

Exemplul 1: Program compilat cu o parte nescrisă

Plata cu cardul și cea cu transfer sunt scrise, plata în rate nu. Programul se compilează și funcționează cât timp nu se ajunge la ramura cu todo!.

enum Plata {
    Card,
    Transfer,
    Rate(u32),
}

fn comision(p: &Plata, suma: f64) -> f64 {
    match p {
        Plata::Card => suma * 0.015,
        Plata::Transfer => 2.5,
        Plata::Rate(luni) => todo!("comisionul pentru {luni} rate"),
    }
}

fn main() {
    let plati = [Plata::Card, Plata::Transfer];
    for p in &plati {
        println!("Comision: {:.2} lei", comision(p, 400.0));
    }
    let _ = Plata::Rate(6);
}

Rezultat

Comision: 6.00 lei
Comision: 2.50 lei

Exemplul 2: Ce se întâmplă dacă se ajunge la todo!

Același cod, dar acum se cere comisionul pentru plata în 6 rate. Programul se oprește cu not yet implemented și mesajul nostru.

enum Plata {
    Card,
    Rate(u32),
}

fn comision(p: &Plata, suma: f64) -> f64 {
    match p {
        Plata::Card => suma * 0.015,
        Plata::Rate(luni) => todo!("comisionul pentru {luni} rate"),
    }
}

fn main() {
    println!("Card: {:.2} lei", comision(&Plata::Card, 400.0));
    println!("Rate: {:.2} lei", comision(&Plata::Rate(6), 400.0));
}

Rezultat (programul se oprește cu panică)

Card: 6.00 lei

thread 'main' (14810905) panicked at main.rs:9:30:
not yet implemented: comisionul pentru 6 rate
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

Exemplul 3: unimplemented! pentru o operație nesuportată

Bilet implementează trait-ul Produs, dar un bilet nu are greutate. Metoda există doar ca să satisfacă trait-ul și nu este apelată niciodată pentru bilete.

trait Produs {
    fn pret(&self) -> f64;
    fn greutate_kg(&self) -> f64;
}

struct Carte;
struct Bilet;

impl Produs for Carte {
    fn pret(&self) -> f64 { 45.0 }
    fn greutate_kg(&self) -> f64 { 0.4 }
}

impl Produs for Bilet {
    fn pret(&self) -> f64 { 80.0 }
    fn greutate_kg(&self) -> f64 {
        unimplemented!("biletele electronice nu au greutate")
    }
}

fn main() {
    let carte = Carte;
    let bilet = Bilet;
    println!("Carte: {} lei, {} kg", carte.pret(), carte.greutate_kg());
    println!("Bilet: {} lei", bilet.pret());
}

Rezultat

Carte: 45 lei, 0.4 kg
Bilet: 80 lei

Greșeli frecvente

  • Uiți un todo! într-o ramură rar folosită: totul pare să meargă până când, în producție, cineva ajunge acolo. Caută „todo!” în proiect înainte de livrare.
  • Folosești todo! unde ar trebui un Result cu eroare: pentru cazuri neacceptate permanent, returnează o eroare clară sau folosește unimplemented! cu un mesaj.

Comenzi înrudite

  • unreachable!: Marchează un loc din cod la care execuția nu ar trebui să ajungă niciodată; dacă ajunge totuși, dă panică.
  • panic!: Oprește firul de execuție curent cu un mesaj de eroare. Se folosește pentru situații care nu ar trebui să apară niciodată (bug-uri).
  • match: Compară o valoare cu o listă de tipare și execută ramura primului care se potrivește. Compilatorul verifică dacă toate cazurile sunt tratate.
  • trait: Definește un set de metode pe care un tip promite să le aibă. Seamănă cu interfețele din alte limbaje și stă la baza generics.