Zwięzła Kontrola Przepływu z if let
Składnia if let
łączy if
i let
, by obsłużyć wartości pasujące do wzorca. Składnia ta jest zwięzła, ale (bez powtarzania if let
) pozwala podać tylko jeden wzorzec.
Rozważmy program z listingu 6-6, który dopasowuje wartość zmiennej config_max
typu Option<u8>
, ale chce wykonać kod tylko jeśli ta wartość jest wariantem Some
.
fn main() { let config_max = Some(3u8); match config_max { Some(max) => println!("Maksimum jest ustawione na {}", max), _ => (), } }
Jeśli wariantem jest Some
, to wypisujemy zawartą w nim wartość przypisując ją uprzednio do zmiennej max
we wzorcu.
Z wariantem None
nie chcemy nic robić. Aby spełnić jednak wymóg wyczerpywalności wyrażenia match
, musimy dodać niewiele znaczące, szablonowe _ => ()
po przetworzeniu tylko jednego wariantu, co jest irytuje.
W zamian możemy napisać to samo krócej używając if let
.
Następujący kod zachowuje się tak samo jak match
z listingu 6-6:
fn main() { let config_max = Some(3u8); if let Some(max) = config_max { println!("Maksimum jest ustawione na {}", max); } }
Składnia if let
przyjmuje wzorzec i wyrażenie oddzielone znakiem równości.
Działa tak samo jak match
, gdzie wyrażenie jest podane do match
, a wzorzec jest jego pierwszą odnogą.
W tym przypadku wzorzec to Some(max)
, a max
zostaje zainicjowane wartością z wnętrza Some
.
Możemy wtedy użyć max
w ciele bloku if let
w taki sam sposób, w jaki użyliśmy max
w odpowiedniej odnodze match
.
Kod w bloku if let
nie jest uruchamiany, jeśli wartość nie pasuje do wzorca.
Używanie if let
oznacza mniej pisania, mniej wcięć i mniej niewiele znaczącego, szablonowego kodu.
Jednakże, w stosunku do match
, tracimy sprawdzanie wyczerpywalności.
Wybór pomiędzy match
a if let
zależy tego, co jest dla nas w danej sytuacji ważniejsze, uzyskanie zwięzłości czy sprawdzanie wyczerpywalności.
Innymi słowy, można myśleć o if let
jako o składniowym lukrze dla match
, który uruchamia kod tylko gdy wartość pasuje do podanego wzorca, równocześnie nie robiąc nic gdy nie pasuje.
Można także do if let
dołączyć else
.
Blok kodu stojący za else
pełni taką samą rolę, jak blok dla odnogi _
w wyrażeniu match
równoważnym do danego if let
z else
.
Proszę sobie przypomnieć definicję typu wyliczeniowego Coin
z listingu 6-4, w którym wariant Quarter
posiada wartość UsState
.
Za pomocą następującego wyrażenia match
możemy policzyć wszystkie widziane monety niebędące ćwiartkami, jednocześnie informując o stanie, z którego pochodzą ćwiartki:
#[derive(Debug)] enum UsState { Alabama, Alaska, // --snip-- } enum Coin { Penny, Nickel, Dime, Quarter(UsState), } fn main() { let coin = Coin::Penny; let mut count = 0; match coin { Coin::Quarter(state) => println!("Ćwiartka ze stanu {:?}!", state), _ => count += 1, } }
To samo możemy też uzyskać za pomocą wyrażenia if let
z else
:
#[derive(Debug)] enum UsState { Alabama, Alaska, // --snip-- } enum Coin { Penny, Nickel, Dime, Quarter(UsState), } fn main() { let coin = Coin::Penny; let mut count = 0; if let Coin::Quarter(state) = coin { println!("Ćwiartka ze stanu {:?}!", state); } else { count += 1; } }
O if let
warto pamiętać w sytuacji, w której wyrażenie logiki za pomocą match
jest zbyt rozwlekłe.
Podsumowanie
Pokazaliśmy, jak używać enumeracji do tworzenia typów, których zmienne mogą być jedną z zestawu wyliczonych wartości.
Wskazaliśmy też, jak typ Option<T>
biblioteki standardowej wykorzystuje systemu typów, aby uniknąć błędów.
W zależności od tego, ile przypadków trzeba obsłużyć, można użyć match
lub if let
do wyodrębnienia i użycia wartości zawartych wewnątrz wariantów enuma.
Programy Rusta mogą teraz wyrażać koncepcje w danej domenie za pomocą struktur i enumów. Utworzenie niestandardowych typów i użycie ich w API zapewnia bezpieczeństwo: kompilator dba o to, aby funkcje otrzymywały tylko wartości oczekiwanego typu.
Przejdźmy teraz do omówienia modułów Rusta, które pozwalają wyrazić API, które jest dobrze zorganizowane, proste w użyciu i eksponuje tylko to, czego potrzebują użytkownicy.