Spis treści

TIL: Rust 1. Enumy, matche i wszechobecne znaki zapytania

Spis treści

Cofnijmy się nieco w przeszłość. Uważam, że dobrze jest przypomianć sobie podstawy, kiedy próbuje się dość szybko robić progres. I nie ma znaczenia w jakiej umiejętności. Więc pogadam sobie o enumach, matchach i operatorze ? w Rust. Dlaczego tak?

Mam takie wrażenie, że warto rozmawiać o tematach które są trudne. A przynajmniej kiedyś były. Bo właśnie te trzy kwestie były jednym z powodów mojego “odbicia się” od tego jeżyka w przeszłości. Jednak ta ściana już nie istnieje. I to jest super. Więc skoro ta ściana podstaw została już zburzona, to pogadajmy o niej. Dla praktyki i przypominania podstaw.

Enumy

Enum to typ wyliczenionyw wróznych językach programowania. Wyliczeniowy, bo pozawal na “wyliczenie” wszystkich dozwolonych wartości. I jak się okazuje jedna z najczęsniej pojawiającyhc się konstrukcji w samym Rust’cie (chociaż nie zawsze jawnie).

Najczęstsze przykłady do konstrukcja Result (oznaczająca wynik operacji która może się nie udać):

enum Result<T,E> {
    Ok<T>,
    Err<E>,
}

oraz Option (stwierdzająca czy dana wartość istnieje czy nie).

enum Option<T> {
    Some<T>
    None,
}

Match

Z kolei match to instrukcja przypominająca switch z bardziej klasycznych języków programowania. Czyli banał - sprawdzamy, czy wartość zmiennej występuje w zdefiniowanej w match liście wartości. I jeżeli tak to program wykonuje na tej wartości jakieś operacje.

Najlepszy przykład, to obsługa błędów z funkcji - czyli połączenie z enumem Result -> tutaj funkcja run która zwraca type Result. To bardoz prosty wzorzec globalnego handlera błędów. Jeżeli funkcja run zwróci Ok, to program zakończy się z kodem 0, a jeżeli Err to wypisze błąd i zakończy się z kodem 1.

fn main() {
    let app = run();
    match handler {
        Ok(app) => exit(0),
        Err(e) => {
            eprintln!("Error: {e}");
            exit(1);
        }
    }
}

Powyższy wzorzec pozwala też na stosowanie operatora znaku zapytania wewnątrz funkcji run(). I właśnie to było miejsce mojego pierwszego odbicia się od Rusta.

Znaki zapytania

Czyli co robi ten cholerny znak zapytania? Występuje co chwila, ale kiedy sam próbowałem go stosować - bład. Próba zmiany - dalej błędy.

Oczywiście można powiedzieć, że “wystarczyłoby przeczytać co mówi kompilator”. I do pewnego stopnia jest to prawda. Ale jeżeli próboujesz przejść z dynamicznie typowanego języka, jakim jest Python (uwielbiam!) do typu statycznego, i w dodatku tak bardzo wymagającego jak Rust, to można się zniechęcić. I nie chodzi tylko o komunikaty kompilatora - które są bardzo szczegółowe i pomocne (chociaż mam wrażenie, że w prostych programach i przy niewielkim odświadczeniu, ten poziom precyzji bardziej przeszkadza niż pomaga).

Dla mnie - większy problem stanowiła zmiana sposobu myślenia o sposobach obsługi błędów.

fn run() {
    jakasFunkcjaRzucającaBłędem()?;
    Ok()
}
// Błąd kompliacji

Myślenie o błędach w Rust jest prostsze - bład to wartość, która może być zwrócona przez funkcję, i czasem przyjmować inne wartości. Nie ma żadnego niewidocznego mechanizmu, który kieruje program w różne miejsca. I na tym polegał właśnie problem. Ten mechanizm jest tak prosty, że wtedy wydawał się aż zbyt prosty. Na siłę szukałem jakiegoś haczyka - w końcu przywyczajenia z nieustannego łapania wyjątków i ich obsługiwania jeden po drugim w Pythonie robiły swoje.

Wracając do operatora ? - żeby móc go użyć, funkcja musi zwracać typ Result. I to jest właśnie ten haczyk. Jeżeli funkcja zwraca typ Result, operator ? pozwala przekazać błąd do funkcji wywołującej. Jeżeli zwraca Ok(), to program jest kontynuowany.

Czyli konieczna poprawka:

fn run() -> Result<(), AppError> { // Dodany typ zwracany Result
    jakasFunkcjaRzucającaBłędem()?;
    Ok(()) // Typ pusty zwracany w przypadku sukcesu - jak w definicji
}
// Teraz już działa

I oczywiście - prawdopodobnie te kilka lat temu wystarczyłoby przeczytać komunikat kompilatora. A z doświadczenia wiem, że czasami najprostsze błedy są najtrudniejsze do zdebugowania. I właśnie dlatego warto chociaż od czasu przypomnieć sobie podstawy.