Dopasowanie wzorca
Konstrukcja match
- Dopisz do enumeracji z poprzedniego ćwiczenia (typ
UIEvent
) funkcję obsługującą dane zdarzenia:fn call(event : UIEvent) { match event { ButtonClicked => println!("Button clicked"), // simple match Scroll(x) => println!("Scroll {:?}", x), // attribute extraction KeyPressed(ch) => { // whole block let up_ch = ch.to_uppercase(); println!("Key pressed: {}", up_ch); } } } fn main() { let clicked = ButtonClicked; let scroll = Scroll(Direction::Down); let key_pressed = KeyPressed('b'); call(clicked); call(scroll); call(key_pressed); }
Zasady działania konstrukcji match
?
- przetwarzania od góry do dołu,
- brak zachowania fall-through - dopasowanie do jednego wzorca, kończy dalsze przetwarzanie,
- konieczność dopasowania co najmniej jednego warunku; reguła ta sprawdzana jest na etapie kompilacji,
match
jest wyrażeniem, a więc może zwracać wartość; typ zwracanej wartości musi być wspólny dla wszystkich wariantów.
Dopasowanie wartości
-
Konstrukcja
match
może zostać wykorzystana również do dopasowania zwykłych wartości (nie tylko typów wyliczeniowych). Przykładem może być tutaj dopasowanie liczby. Należy jednak pamiętać, że musimy zapewnić dopasowanie wszystkich możliwych wartości. W tym celu, aby nie musieć pisać wszystkich wartości z osobna, można wykorzystać symbol_
:fn main() { let x = 2u8; match x { 1 => println!("one"), 2 => println!("two"), 3 => println!("three"), _ => println!("other number") } }
-
Wzorce wielokrotne pozwalają na dopasowanie do jednej operacji wielu wartości:
fn main() { let x = 2u8; match x { 1 | 2 => println!("one or two"), 3 | 4 => println!("three or four"), _ => println!("anything else"), } }
-
Dopasowanie zakresów daje jeszcze większe możliwości dopasowania wartości
fn main() { let x = 2u8; match x { 1..=9 => println!("easy"), 10..=99 => println!("medium"), 100..=999 => println!("large"), _ => println!("anything else"), } }
Ekstrakcja / dekonstrukcja wartości
- Dopasowanie wzorców może zostać również wykorzystane do dekonstrukcji struktury i zdefiniowanie warunków dotyczących wartości jej atrybutów. Dekonstrukcja pozwala na wykorzystanie atrybutu bezpośrednio w bloku określającym operację wykonywana po dopasowaniu wzorca. Przykładowo, na poniższym fragmencie kodu zdefiniowano warunki dla struktury
Point
struct Point { x : i32, y : i32 } fn main() { let p = Point { x: 0, y: 7 }; match p { Point { x, y: 0 } => println!("On the x axis at {x}"), Point { x: 0, y } => println!("On the y axis at {y}"), Point { x, y } => { println!("On neither axis: ({x}, {y})"); } } }
- Możliwe jest również pominięcie atrybutów, które nie są istotne z punktu widzenia dopasowania wzorca
fn main() { struct Point { x: i32, y: i32, z: i32, } let origin = Point { x: 0, y: 0, z: 0 }; match origin { Point { x, .. } => println!("x is {}", x), _ => () } }
Instrukcja warunkowa w dopasowaniu wzorca
- Reguły dopasowujace mogą dodatkowy warunek z wykorzystaniem instrukcji
if
(tzw. strażnik, guard)fn main() { let num = Some(4); match num { Some(x) if x % 2 == 0 => println!("The number {} is even", x), Some(x) => println!("The number {} is odd", x), None => (), } }
- Kombinacja strażnika z dopasowaniem wielokrotnym
fn main() { let x = 4; let y = false; match x { 4 | 5 | 6 if y => println!("yes"), _ => println!("no"), } }
Konstrukcja if let
- Konstrukcja
if let
jest wykorzystywana wtedy gdy chcemy dopasować tylko jeden wzorzec, a całą resztę pominać. Na przykład mowa o takiej sytuacji:fn main() { let x = Some(2); match x { Some(x) => println!("{}"), None => () } }
- Powyższa konstrukcję można zastąpić w następujący sposób:
fn main() { let x = Some(2); if let Some(x) = x { println!("{}", x); } }
- Zarówno w konstrukcji
match
orazlet if
wzorzec może zawierać konkretne wartości:fn main() { let x = Some(2); if let Some(2) = x { println!("{}", x); } }
Więcej informacji o dopasowaniu wzorców znajdziesz na stronie: https://doc.rust-lang.org/book/ch18-03-pattern-syntax.html
Ćwiczenie
Zdefiniuj typ wyliczeniowy Message
oraz funkcję process
na bazie ich użycia w teście test_match_message_call
, przy użyciu zdefiniowanych metod.
enum Message {
// TODO: implement the message variant types based on their usage below
}
struct Point {
x: u8,
y: u8,
}
struct State {
color: (u8, u8, u8),
position: Point,
quit: bool,
message: String,
}
impl State {
fn change_color(&mut self, color: (u8, u8, u8)) {
self.color = color;
}
fn quit(&mut self) {
self.quit = true;
}
fn echo(&mut self, s: String) {
self.message = s
}
fn move_position(&mut self, p: Point) {
self.position = p;
}
fn process(&mut self, message: Message) {
// TODO: create a match expression to process the different message variants
// Remember: When passing a tuple as a function argument, you'll need extra parentheses:
// fn function((t, u, p, l, e))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_match_message_call() {
let mut state = State {
quit: false,
position: Point { x: 0, y: 0 },
color: (0, 0, 0),
message: "hello world".to_string(),
};
state.process(Message::ChangeColor(255, 0, 255));
state.process(Message::Echo(String::from("Hello world!")));
state.process(Message::Move(Point { x: 10, y: 15 }));
state.process(Message::Quit);
assert_eq!(state.color, (255, 0, 255));
assert_eq!(state.position.x, 10);
assert_eq!(state.position.y, 15);
assert_eq!(state.quit, true);
assert_eq!(state.message, "Hello world!");
}
}