Rust opiera się na jawnej kontroli błędów: wyjątki są nieobecne, zamiast tego używa się wartości zwracanej Result<T, E>. Zapewnia to bezpieczeństwo i przewidywalność kodu.
Historia zagadnienia:
Wiele języków poszło drogą wyjątków, co prowadziło do nieoczekiwanych sytuacji i konieczności jawnego obsługiwania wyjątków w czasie działania. Rust od samego początku postawił na kontrolę przez typy, wszystkie błędy muszą być częścią sygnatury funkcji.
Problem:
Głównym zadaniem jest pisanie kodu, w którym błędy nie są ignorowane i nie są ukrywane, nie ma „padających” funkcji, ale kod pozostaje kompaktowy i czytelny. Należy prawidłowo propagować błędy wyżej, nie tracąc informacji o ich typie, i nie mylić logiki.
Rozwiązanie:
Kluczowym narzędziem jest typ Result<T, E> i operator ?, który automatycznie „rozwija” wynik: w przypadku błędu następuje natychmiastowe wyjście z funkcji z zwróceniem błędu, a w przypadku sukcesu zwracana jest wartość.
Przykład kodu:
fn read_number(file: &str) -> Result<i32, std::io::Error> { let content = std::fs::read_to_string(file)?; let num: i32 = content.trim().parse()?; Ok(num) }
Kluczowe cechy:
.map_err() i inne metody)Czy można używać ? w funkcjach zwracających Unit (void)?
Nie, operator ? jest dozwolony tylko wewnątrz funkcji, które zwracają Result lub Option. Jeśli funkcja zwraca (), nie można używać ?.
Co się stanie, jeśli typy błędów w kilku wywołaniach z ? są różne?
Powstaje błąd kompilacji: typ błędu musi być jednoznacznie określony. Należy albo sprowadzić wszystkie błędy do jednego typu poprzez .map_err(), albo używać thiserror, albo opisać typ enum-opakowanie na poziomie API. Przykład:
fn foo() -> Result<_, MyError> { let a = bar()?; let b = baz().map_err(MyError::Baz)?; Ok(…) }
Jakie niebezpieczeństwo niesie ze sobą .unwrap() w wewnętrznej logice?
Powszechne jest błędne przekonanie, że w „głównym” kodzie można swobodnie używać unwrap(), bo „na pewno nie spadnie”. W rzeczywistości nawet mały niewidoczny błąd doprowadzi do paniki czasu wykonania — naruszy to bezpieczeństwo programu.
unwrap/expect, szczególnie w wewnętrznej logiceW kodzie produkcyjnym pozostawiono wiele unwrap() po szybkim debugowaniu. W rezultacie aplikacja padała przy każdym błędzie parsowania z powodu niepoprawnego wejścia od użytkownika.
Zalety:
Wady:
Użyto pełnego stosu Result<T, E> z explicite opisanymi błędami i stosowano tylko ? oraz .map_err(), zachowując wszystkie informacje o błędzie.
Zalety:
Wady: