Propagacja błędów
Zwracanie błędów z funkcji
-
Przypomnijmy w jaki sposób funkcja może zwracać wartość, która może zawierać błąd
impl Rectangle { fn new(width : f64, height : f64) -> Result<Rectangle, String> { if width < 0.0 || height < 0.0 { Err("Rectangle cannot have negative width or height".to_string()) } else { Ok(Rectangle { width, height }) } } }
-
Napisz funkcję, która przyjmuje rozmiar dwóch prostokątów i sprawdza, który jest większy (ie. ma większą powierzchnię):
fn bigger(w1: f64, h1 : f64, w2 : f64, h2 : f64) -> Result<Rectangle, String> { let r1 = Rectangle::new(w1, h1); let r2 = Rectangle::new(w2, h2); match (r1, r2) { (Ok(rec1), Ok(rec2)) => { if rec1.area() > rec2.area() { Ok(rec1) } else { Ok(rec2) } }, _ => Err("Rectangle cannot have negative width or height".to_string()), } }
-
Aby sprawdzić czy funkcja działa poprawnie napisz dwa proste testy jednostkowe
#[test] fn test_bigger() { // given let w1 = 1.0; let h1 = 2.0; let w2 = 3.0; let h2 = 4.0; // when let r = bigger(w1, h1, w2, h2); // then assert!((r.unwrap().area() - (w2 * h2)).abs() < f64::EPSILON); } #[test] fn test_bigger_err() { // given let w1 = 1.0; let h1 = -2.0; // wrong width let w2 = 3.0; let h2 = 4.0; // when let r = bigger(w1, h1, w2, h2); // then assert!(r.is_err()); }
-
Uruchom i sprawdź czy kod jest poprawny.
Operator ?
Metoda bigger
może zostać uproszczona za pomocą operatora ?
, który działa podobnie jak w przypadku typu Option
, tzn.:
- jeśli zostaje wywołany na wartości
Ok
, to zwraca wartość wynikową (enkapsulowaną przezOk
) - jeśli zostaje wywołany na wertości
Err
, to od razu wychodzi z bieżącej funkcji/metody i zwraca (propaguje) błąd Operator?
może być wykorzystany tylko i wyłącznie w funkcjach lub metodach, które zwracają typResult
oraz może być wywołany na wartościach, które posiadają zgodny typ błędu
- Przepisz metodę
bigger
z wykorzystaniem operatora?
:fn bigger(w1: f64, h1 : f64, w2 : f64, h2 : f64) -> Result<Rectangle, String> { let r1 = Rectangle::new(w1, h1)?; let r2 = Rectangle::new(w2, h2)?; if r1.area() > r2.area() { Ok(r1) } else { Ok(r2) } }
- Uruchom testy i sprawdź czy funkcja działa poprawnie
Obsługa błędów różnego typu
- Napiszmy funkcję, która z prostego pliku tekstowego przeczyta długość i szerokość prostokąta oraz utworzy jego instancję:
fn read_from_file(path : &str) -> Result<Rectangle, ...> { let s = fs::read_to_string(path)?; let mut iter = s.split_whitespace(); let width : f64 = iter.next().ok_or("Cannot convert string to width".to_string())?.parse()?; let height : f64 = iter.next().ok_or("Cannot convert string to height".to_string())?.parse()?; Rectangle::new(width, height) }
- Metoda
read_to_string
zwraca błąd typuio::Error
, z kolei metodaRectangle::new
zwraca błąd typuString
. Sprawdź jakiego typu błędy zwraca metodaok_or
orazparse
. - W takim razie jakiego typu powinna być wartość zwracana przez metodę
read_from_file
? Jakiego typu powinien być błąd? - Napisz test jednostkowy, który sprawdzi czy dla nieistniejącego pliku zostanie zwrócony błąd odpowiedniego typu (ie.
io::Error
)#[test] fn test_read_from_not_existing_file() { // given let path = "not-existing-file.txt"; // when let r = Rectangle::read_from_file(path); // then match r { Err(ref e) if e.is::<std::io::Error>() => println!("ok"), _ => panic!() } }
Obsługa dynamicznego typu jest problematyczna, ponieważ Rust nie dostarcza prostych mechanizmów sprawdzania aktualnego typu błędu, a tym samym obsługa ta sprowadza się zazwyczaj do wywołania zachowań wspólnych dla wszystkie rodzajów błędów (typu std::error::Error
).
Biblioteka anyhow
Aby odpowiedzieć na typowe problemy z obsługą błędów różnego typu, bibliotek anyhow wprowadza własną implementację typu Result
, która domyślnie zawiera błąd typu anyhow::Error
. Biblioteka ta dostarcza też makra umożliwiające tworzenie rezultatów (zawierających błędy) takich jak anyhow!
i bail!
.
-
Zanim użyjesz biblioteki, dodaj do niej zależność w pliku
Cargo.toml
:[dependencies] anyhow = "1.0"
-
Stosując bibliotekę anyhow metoda
read_from_file
wyglądałaby następująco:fn read_from_file(path : &str) -> Result<Rectangle> { let s = fs::read_to_string(path)?; let mut iter = s.split_whitespace(); let width : f64 = iter.next().ok_or(anyhow!("Cannot convert string to width"))?.parse()?; let height : f64 = iter.next().ok_or(anyhow!("Cannot convert string to height"))?.parse()?; Ok(Rectangle::new(width, height)?) }
-
Aby kod się skompilował popraw metodę
new
, tak aby zwracała typanyhow::Result
. -
Popraw też wszystkie testy, która przestały się wykonywać.