Historia pytania
Operator match w Rust to potężne narzędzie do dopasowywania wzorców, zaczerpnięte z języków funkcyjnych. W przeciwieństwie do odpowiedników w C/C++, w Rust przeprowadzana jest ścisła kontrola pełności (exhaustiveness checking), co oznacza, że dla każdej możliwej opcji muszą być zdefiniowane odpowiednie gałęzie.
Problem
Błędy podczas używania match najczęściej wynikają z niewłaściwego uwzględnienia wszystkich wariantów typu (np. enum), błędnego działania z guardami (warunkami dla gałęzi) lub złożonej, zagnieżdżonej struktury. Niewłaściwe przetwarzanie prowadzi do błędów na etapie kompilacji lub do ukrytej, błędnej logiki.
Rozwiązanie
_).if po wzorcu).Przykład kodu:
enum Shape { Circle(f64), Rectangle { width: f64, height: f64 }, } fn print_area(s: Shape) { match s { Shape::Circle(r) if r > 0.0 => println!("area = {}", 3.14 * r * r), Shape::Rectangle { width, height } if width > 0.0 && height > 0.0 => println!("area = {}", width * height), _ => println!("invalid shape"), } }
Kluczowe cechy:
Czy kolejność gałęzi w match ma wpływ na wykonanie?
Tak, kolejność gałęzi jest ważna: gdy tylko znajdzie się pierwsze dopasowanie, kolejne nie są sprawdzane. To jest szczególnie krytyczne w przypadku pattern guard – jeśli wcześniej znajduje się gałąź z guard, przechwyci ona wartość przed catch-all.
Czy catch-all (_) jest wymagany przy match na enum?
Nie, jeśli jawnie rozpatrzono wszystkie przypadki (a same opcje deklaracji typu nie zmienią się w czasie). Jednak catch-all jest potrzebny przy pracy z typami, które mogą zyskać dodatkowe wartości lub gdy nie chcemy jawnie przetwarzać wszystkich gałęzi.
Czy w jednej gałęzi match można używać kilku wzorców (alternatives)?
Tak. Poprzez pionową kreskę (|) można łączyć wzorce:
match x { 1 | 2 | 3 => println!("one, two or three"), _ => println!("something else"), }
_ zbyt wcześnie (przed specyficznymi gałęziami)Programista napisał match z catch-all na początku i nie zdołał poprawnie obsłużyć specyficznych przypadków.
Zalety:
Program kompiluje się.
Wady:
Specyficzna logika nigdy nie działa, część kodu nie jest pokrywana.
Jawna obsługa wszystkich wariantów enum, catch-all tylko jako ostatnia gałąź, oddzielne guardy dla nietypowych przypadków.
Zalety:
Przewidywalność, kompilator pomaga nie zapomnieć o wariancie, łatwe rozszerzenie typów.
Wady:
Dodatkowy szablonowy kod przy dużej liczbie wariantów.