Давайте рассмотрим выражение x = z;. Предположим у нас есть переменные “x” и “z” со значениями и затем мы “иксу присваиваем зет”. При этом все нужные условия соблюдены и все работает без ошибок. Что происходит в этот момент? Сначала происходит проверка на соответствие типу, потом мы присваиваем значение. Нужно сказать, что при x = 5; происходит тоже самое. Проверка на соответствие типу происходит всегда, чтобы не допустить такого: let x:i32 = 5.0; Мы рассмотрели простой тип, но тоже самое происходит и со сложными типами, и пользовательскими в том числе. Например, при присвоении структур, происходит проверка на соответствие типов, имеющихся в структуре переменных, которые в свою очередь тоже могут быть структурами. А значит нам нужно проверить довольно сложную иерархию, после чего присвоить нужные значения нужным переменным в этой иерархии. Происходит что-то подобное на работу с регулярными выражениями, когда набор типов представляет из себя образец данных. Имея такой инструмент, почему бы не использовать его для других целей, например, в составе оператора switch из C/C++?
Напоминаю, что оператор switch заменяет собой набор операторов if-else if-else и помогает выполнить нужное действие, которое соответствует условию равенства определенному значению. Подробно останавливаться не будем, скажу только, что в C++ условие равенства — это просто true-false. Мы можем проверять только простые переменные по отдельности, пусть они даже находятся в составе сложных типов. А вот было бы здорово проверить на входе сложный тип целиком, который в свою очередь может представлять иерархию типов, о чем мы говорили выше. Это и есть проверка данных по образцу в Rust. Замечу, что мы можем проверять на соответствие между собой образцы только одинакового типа, зато образцы могут содержать любые варианты в рамках специальных правил. Например, мы можем использовать конкретные значения, либо указать имя внутренней переменной, которая будет содержать значение из поданного на вход для проверки типа, либо символ “_”, который обозначает – любое значение. При совпадении имен внутренней переменной и поданной на вход, можно упростить запись, вместо “x: x” использовать просто “x”. Также некоторые образцы дублируют по смыслу друг друга. Например, “x: y” соответствует “x: _”, только в первом варианте происходит присвоение значения из “x” во внутреннюю “y”, а во втором нет, но по смыслу они одинаковы – любое значение. Выход из проверки происходит при первом совпадении, поэтому если в середине будет стоять образец с “_”, то последующие варианты могут быть никогда не достигнуты.
Еще одно отличное свойство match про которое нельзя не сказать, это полное покрытие вариантов, при котором мы обязаны перечислить все возможные варианты для проверки. Благодаря этому компилятор сообщит об ошибке, если мы случайно забудем проверить какой-то из вариантов, что значительно повысит надежность кода.
При проверке на соответствие образцу пользовательских типов, которые в свою очередь, могут содержать тяжелые данные, логично принимать такие данные во внутреннюю переменную по ссылке, иначе произойдет перемещение или копирование ресурса. Но тут есть один нюанс, мы не можем использовать “&”, при котором произойдет передача не данных, а самого указателя. Вместо этого есть специально выделенное слово “ref”. Смотрим пример.
P.S. Возвращаясь к нашему Result, который может содержать пользовательский тип в качестве правильного варианта, мы можем проверить на соответствие несколько правильных вариантов с разными образцами пользовательского типа.
#[derive(Debug)]
struct Point {
x: i32,
y: i32,
}
fn main() {
let (x, y, z) = (1, 2, 3);
println!("destructuring tuple (1,2,3) to x={}, y={}, z={}", x, y, z);
let (_, sub_t @ (_, _)) = (1, (2, 3));
println!("destructuring sub-tuple from (1,(1,2)) to sub_t={:?}", sub_t);
let (v, ..) = (1, (2, 3));
println!("destructuring first element from tuple (1,(1,2)) to v={:?}", v);
let mut p1 = Point { x: 0, y: 5 };
println!("\nmatch {:?} with:", p1);
let X = 0;
match p1 {
Point { x: 5, y: 5 } => println!("pattern: 5, 5"),
Point { x: z, y: 5 } if z != 0 => println!("pattern: z={}, 5", z), //match guard
Point { x, y: 5 } if x == X => println!("pattern: x={}, 5", x), //match guard
Point { x: _, y: 5 } => println!("pattern: _, 5"),
_ => (),
}
let mut p2 = Point { x: 1, y: 10 };
println!("\nmatch {:?} with:", p2);
match p2 {
Point { x: ref x, y: 10 } => println!("pattern: ref x={}, 10", x),
Point { ref x, y: 10 } => println!("pattern: ref x={}, 10", x),
_ => (),
}
}
//Output:
>>destructuring tuple (1,2,3) to x=1, y=2, z=3
>>destructuring sub-tuple from (1,(1,2)) to sub_t=(2, 3)
>>destructuring one element from tuple (1,(1,2)) to v=1
>>
>>match Point { x: 0, y: 5 } with:
>>pattern: x=0, 5
>>
>>match Point { x: 1, y: 10 } with:
>>pattern: ref x=1, 10
Отдельный пример с перечислением (enumeration):
#[derive(Debug)]
pub enum Drive {
Forward(u8),
Turn { slight: bool, right: bool },
Stop,
}
fn main() {
let path = [
Drive::Forward(3),
Drive::Turn { slight: false, right: true },
Drive::Forward(1),
Drive::Stop,
];
println!("path to home: {:?}", path);
for step in path.iter() {
match step {
Drive::Forward(blocks) => {
println!("Drive forward {} blocks", blocks);
}
Drive::Turn { slight, right } => {
println!("Turn {}{}",
if *slight { "slightly" } else { "" },
if *right { "right" } else { "left" }
);
}
Drive::Stop => {
println!("You have reached your home!");
}
}
}
}
//Output:
>>path to home: [Forward(3), Turn { slight: false, right: true }, Forward(1), Stop]
>>Drive forward 3 blocks
>>Turn right
>>Drive forward 1 blocks
>>You have reached your home!
Отдельный пример - матчим параметр (&self) метода damage():
#[derive(Debug)]
pub enum Status {
Alive { health: u8 }, //u8 - 0..255
Dead,
}
#[derive(Debug)]
pub struct Archer {
name: String,
status: Status,
}
impl Archer {
pub fn new(_name: String, _health: u8) -> Self {
Archer { name: _name, status: Status::Alive { health: _health } }
}
pub fn damage(&mut self, points: u8) {
match self.status {
Status::Alive { health: x } => self.status = Status::Alive { health: x - points },
Status::Dead => {}
}
}
}
fn main() {
let mut obj1 = Archer::new("Bob".to_string(), 100);
obj1.damage(35);
println!("{:?}", obj1.status)
}
//Output:
>>Alive { health: 65 }
Перегуд В.
Комментариев нет:
Отправить комментарий