Wszystkie Miejsca, w Których Można Używać Wzorców
Wzorce pojawiają się w wielu miejscach w Rust, a Ty używałeś ich wiele razy, nie zdając sobie z tego sprawy! Ta sekcja omawia wszystkie miejsca, w których wzorce są prawidłowe.
Ramiona match
Jak omówiono w Rozdziale 6, używamy wzorców w ramionach wyrażeń match. Formalnie wyrażenia match są definiowane jako słowo kluczowe match, wartość do dopasowania i jedno lub więcej ramion dopasowania, które składają się ze wzorca i wyrażenia do uruchomienia, jeśli wartość pasuje do wzorca tego ramienia, w ten sposób:
match WARTOŚĆ {
WZORZEC => WYRAŻENIE,
WZORZEC => WYRAŻENIE,
WZORZEC => WYRAŻENIE,
}
Na przykład, oto wyrażenie match z Listy 6-5, które dopasowuje wartość Option<i32> w zmiennej x:
match x {
None => None,
Some(i) => Some(i + 1),
}
Wzorce w tym wyrażeniu match to None i Some(i) po lewej stronie każdej strzałki.
Jednym z wymagań dla wyrażeń match jest to, że muszą być wyczerpujące w tym sensie, że wszystkie możliwości dla wartości w wyrażeniu match muszą być uwzględnione. Jednym ze sposobów zapewnienia, że pokryłeś każdą możliwość, jest posiadanie wzorca catch-all dla ostatniego ramienia: na przykład nazwa zmiennej pasująca do dowolnej wartości nigdy nie może się nie powieść i tym samym pokrywa każdy pozostały przypadek.
Konkretny wzorzec _ pasuje do wszystkiego, ale nigdy nie wiąże się ze zmienną, więc często jest używany w ostatnim ramieniu match. Wzorzec _ może być użyteczny, gdy chcesz zignorować dowolną nieokreśloną wartość, na przykład. Omówimy wzorzec _ bardziej szczegółowo w sekcji „Ignorowanie wartości we wzorcu” w dalszej części tego rozdziału.
Instrukcje let
Przed tym rozdziałem jawnie omawialiśmy używanie wzorców tylko z match i if let, ale w rzeczywistości używaliśmy wzorców również w innych miejscach, w tym w instrukcjach let. Na przykład, rozważ to proste przypisanie zmiennej za pomocą let:
#![allow(unused)]
fn main() {
let x = 5;
}
Za każdym razem, gdy używałeś instrukcji let w ten sposób, używałeś wzorców, chociaż mogłeś tego nie zdawać sobie sprawy! Bardziej formalnie, instrukcja let wygląda tak:
let WZORZEC = WYRAŻENIE;
W instrukcjach takich jak let x = 5; z nazwą zmiennej w miejscu PATTERN, nazwa zmiennej jest po prostu szczególnie prostą formą wzorca. Rust porównuje wyrażenie ze wzorcem i przypisuje wszystkie znalezione nazwy. Tak więc, w przykładzie let x = 5;, x jest wzorcem, który oznacza „zwiąż to, co pasuje tutaj, ze zmienną x”. Ponieważ nazwa x jest całym wzorcem, ten wzorzec skutecznie oznacza „zwiąż wszystko ze zmienną x, niezależnie od wartości”.
Aby wyraźniej zobaczyć aspekt dopasowywania wzorców w let, rozważ Listę 19-1, która używa wzorca z let do dekonstrukcji krotki.
fn main() {
let (x, y, z) = (1, 2, 3);
}
Tutaj dopasowujemy krotkę do wzorca. Rust porównuje wartość (1, 2, 3) ze wzorcem (x, y, z) i widzi, że wartość pasuje do wzorca — to znaczy, widzi, że liczba elementów jest taka sama w obu — więc Rust wiąże 1 z x, 2 z y i 3 z z. Możesz myśleć o tym wzorcu krotki jako o zagnieżdżeniu w nim trzech pojedynczych wzorców zmiennych.
Jeśli liczba elementów we wzorcu nie odpowiada liczbie elementów w krotce, ogólny typ nie będzie pasował i otrzymamy błąd kompilacji. Na przykład, Lista 19-2 pokazuje próbę dekompozycji krotki z trzema elementami na dwie zmienne, co nie zadziała.
fn main() {
let (x, y) = (1, 2, 3);
}
Próba skompilowania tego kodu skutkuje błędem typu:
$ cargo run
Compiling patterns v0.1.0 (file:///projects/patterns)
error[E0308]: mismatched types
--> src/main.rs:2:9
|
2 | let (x, y) = (1, 2, 3);
| ^^^^^^ --------- this expression has type `({integer}, {integer}, {integer})`
| |
| expected a tuple with 3 elements, found one with 2 elements
|
= note: expected tuple `({integer}, {integer}, {integer})`
found tuple `(_, _)`
For more information about this error, try `rustc --explain E0308`.
error: could not compile `patterns` (bin "patterns") due to 1 previous error
Aby naprawić błąd, moglibyśmy zignorować jedną lub więcej wartości w krotce za pomocą _ lub .., jak zobaczysz w sekcji „Ignorowanie wartości we wzorcu”. Jeśli problem polega na tym, że mamy zbyt wiele zmiennych we wzorcu, rozwiązaniem jest dopasowanie typów poprzez usunięcie zmiennych, tak aby liczba zmiennych była równa liczbie elementów w krotce.
Warunkowe Wyrażenia if let
W Rozdziale 6 omówiliśmy, jak używać wyrażeń if let głównie jako krótszego sposobu na zapisanie odpowiednika match, który pasuje tylko do jednego przypadku. Opcjonalnie, if let może mieć odpowiadający mu else zawierający kod do uruchomienia, jeśli wzorzec w if let nie pasuje.
Lista 19-3 pokazuje, że możliwe jest również mieszanie i dopasowywanie wyrażeń if let, else if i else if let. Działanie to daje nam większą elastyczność niż wyrażenie match, w którym możemy wyrazić tylko jedną wartość do porównania ze wzorcami. Ponadto Rust nie wymaga, aby warunki w serii ramion if let, else if i else if let były ze sobą powiązane.
Kod na Liście 19-3 określa kolor tła na podstawie serii sprawdzeń kilku warunków. W tym przykładzie stworzyliśmy zmienne z zakodowanymi na stałe wartościami, które w rzeczywistym programie mogłyby pochodzić z danych wejściowych użytkownika.
fn main() {
let favorite_color: Option<&str> = None;
let is_tuesday = false;
let age: Result<u8, _> = "34".parse();
if let Some(color) = favorite_color {
println!("Using your favorite color, {color}, as the background");
} else if is_tuesday {
println!("Tuesday is green day!");
} else if let Ok(age) = age {
if age > 30 {
println!("Using purple as the background color");
} else {
println!("Using orange as the background color");
}
} else {
println!("Using blue as the background color");
}
}
if let, else if, else if let i elseJeśli użytkownik określi ulubiony kolor, ten kolor zostanie użyty jako tło. Jeśli nie określono ulubionego koloru i dziś jest wtorek, kolor tła jest zielony. W przeciwnym razie, jeśli użytkownik poda swój wiek jako ciąg znaków i uda nam się go poprawnie sparsować jako liczbę, kolor będzie fioletowy lub pomarańczowy w zależności od wartości liczby. Jeśli żaden z tych warunków nie zostanie spełniony, kolor tła będzie niebieski.
Ta struktura warunkowa pozwala nam obsługiwać złożone wymagania. Z zakodowanymi na stałe wartościami, ten przykład wydrukuje Using purple as the background color.
Widzisz, że if let może również wprowadzać nowe zmienne, które zacieniają istniejące zmienne w taki sam sposób, jak ramiona match: linia if let Ok(age) = age wprowadza nową zmienną age, która zawiera wartość wewnątrz wariantu Ok, zacieniając istniejącą zmienną age. Oznacza to, że musimy umieścić warunek if age > 30 w tym bloku: nie możemy połączyć tych dwóch warunków w if let Ok(age) = age && age > 30. Nowa zmienna age, którą chcemy porównać z 30, nie jest prawidłowa, dopóki nowy zakres nie zacznie się od nawiasu klamrowego.
Wadą stosowania wyrażeń if let jest to, że kompilator nie sprawdza wyczerpująco wszystkich możliwości, w przeciwieństwie do wyrażeń match. Gdybyśmy pominęli ostatni blok else, a tym samym nie obsłużyli niektórych przypadków, kompilator nie ostrzegłby nas o możliwym błędzie logicznym.
Pętle Warunkowe while let
Podobnie jak w przypadku if let, pętla warunkowa while let pozwala na działanie pętli while tak długo, jak wzorzec pasuje. Na Liście 19-4 pokazujemy pętlę while let, która czeka na wiadomości wysyłane między wątkami, ale w tym przypadku sprawdza Result zamiast Option.
fn main() {
let (tx, rx) = std::sync::mpsc::channel();
std::thread::spawn(move || {
for val in [1, 2, 3] {
tx.send(val).unwrap();
}
});
while let Ok(value) = rx.recv() {
println!("{value}");
}
}
while let do drukowania wartości tak długo, jak rx.recv() zwraca OkTen przykład wypisuje 1, 2, a następnie 3. Metoda recv pobiera pierwszą wiadomość z odbiornika kanału i zwraca Ok(value). Kiedy po raz pierwszy widzieliśmy recv w Rozdziale 16, bezpośrednio rozpakowaliśmy błąd lub wchodziliśmy z nim w interakcję jako z iteratorem za pomocą pętli for. Jak pokazuje Lista 19-4, możemy jednak również użyć while let, ponieważ metoda recv zwraca Ok za każdym razem, gdy nadejdzie wiadomość, dopóki nadawca istnieje, a następnie generuje Err, gdy strona nadawcy się rozłączy.
Pętle for
W pętli for wartość, która bezpośrednio następuje po słowie kluczowym for, jest wzorcem. Na przykład w for x in y, x jest wzorcem. Lista 19-5 demonstruje, jak użyć wzorca w pętli for do dekonstrukcji, czyli rozłożenia, krotki jako część pętli for.
fn main() {
let v = vec!['a', 'b', 'c'];
for (index, value) in v.iter().enumerate() {
println!("{value} is at index {index}");
}
}
for do dekonstrukcji krotkiKod na Liście 19-5 wypisze następujące informacje:
$ cargo run
Compiling patterns v0.1.0 (file:///projects/patterns)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.52s
Running `target/debug/patterns`
a is at index 0
b is at index 1
c is at index 2
Adaptujemy iterator za pomocą metody enumerate tak, aby produkowała ona wartość i indeks dla tej wartości, umieszczone w krotce. Pierwsza wyprodukowana wartość to krotka (0, 'a'). Gdy ta wartość zostanie dopasowana do wzorca (index, value), index będzie 0, a value będzie 'a', drukując pierwszą linię wyjścia.
Parametry Funkcji
Parametry funkcji również mogą być wzorcami. Kod z Listy 19-6, który deklaruje funkcję foo przyjmującą jeden parametr x typu i32, powinien być już znany.
fn foo(x: i32) {
// code goes here
}
fn main() {}
Część x to wzorzec! Tak jak w przypadku let, moglibyśmy dopasować krotkę w argumentach funkcji do wzorca. Lista 19-7 rozdziela wartości w krotce, gdy przekazujemy ją do funkcji.
fn print_coordinates(&(x, y): &(i32, i32)) {
println!("Current location: ({x}, {y})");
}
fn main() {
let point = (3, 5);
print_coordinates(&point);
}
Ten kod wypisuje Current location: (3, 5). Wartości &(3, 5) pasują do wzorca &(x, y), więc x ma wartość 3, a y ma wartość 5.
Możemy również używać wzorców w listach parametrów domknięć w taki sam sposób, jak w listach parametrów funkcji, ponieważ domknięcia są podobne do funkcji, jak omówiono w Rozdziale 13.
Do tej pory widziałeś kilka sposobów użycia wzorców, ale wzorce nie działają tak samo w każdym miejscu, w którym możemy ich użyć. W niektórych miejscach wzorce muszą być nieodrzucalne; w innych okolicznościach mogą być odrzucalne. Następnie omówimy te dwie koncepcje.