Enums
Перечисления
Перечисление — это тип данных, который позволяет определить набор именованных значений (вариантов). Каждый вариант может быть просто именем или содержать дополнительные данные.
enum Money {
Rub,
Kop
}Здесь мы определили перечисление Money с двумя вариантами: Rub, и Kop. Эти варианты не содержат дополнительных данных — они просто имена, которые представляют возможные состояния. В терминах Rust такие варианты без данных часто называют “unit-like” (похожими на Unit), но это не совсем то же самое, что массив или указатели.
Unit
“Unit” в Rust — это специальный тип (), который имеет только одно значение, тоже обозначаемое как (). Это что-то вроде “пустого значения”, которое часто используется, когда функция ничего не возвращает. Например:
fn do_nothing() {
// Ничего не возвращаем, implicitly возвращается ()
}В случае с Money каждый вариант (Rub и Kop) сам по себе не является типом (), но его можно рассматривать как “unit-like”, потому что он не несёт дополнительных данных. Это просто маркер, который говорит: “Я одно из двух состояний”.
Enum в памяти
Внутри памяти Money представлен как небольшое целое число (обычно 1 байт для простых перечислений вроде этого), называемое “дискриминантом”. Этот дискриминант указывает, какой вариант сейчас используется:
Money::Rub→ 0Money::Kop→ 1 Но это внутренняя реализация. Для программиста это просто разные состояния.
match
Аналог switch в других языках, однако, круче: его проверка не сводится к bool, а также реакция на каждое действие может быть блоком:
fn main() {
let number = 42;
match number {
n if n > 0 => println!("Положительное: {}", x),
n if n < 0 => println!("Отрицательное: {}", y),
_ => println!("Ноль"), } }n - привязка (binding). Когда ты пишешь n if n > 0, то говоришь: “возьми значение number и назови его n для этой ветки. Затем проверь условие if n > 0. Если оно истинно, выполни действие”. Обычно match используется с точными значениями (например, 1 => ..., 2 => ...), но добавление if позволяет проверять более сложные условия, как в этом примере (положительное, отрицательное или ноль). Это называется guards (охрана) в Rust.
Ещё пример:
fn main() {
let m = Money::Kop;
println!("Я нашёл кошелёк, а там {}p",match_value_in_kop(m));
}
fn match_value_in_kop(money: Money) -> u8 {
match money {
Money::Rub => 100,
Money::Kop => {
println!("Счастливая копейка!");
1
} } }match как выражение
fn main() {
let number = 3;
let result = match number {
1 => "один",
2 => "два",
_ => "другое",
};
println!("Результат: {}", result); // Вывод: Результат: другое
}Проверка условия и запуск соответствующего метода:
struct State {
color: (u8, u8, u8),
position: Point,
quit: bool,
}
impl State {
fn change_color(&mut self, color: (u8, u8, u8)) {
self.color = color; }
fn quit(&mut self) {
self.quit = true; }
fn echo(&self, s: String) {
println!("{}", s); }
fn move_position(&mut self, p: Point) {
self.position = p; }
fn process(&mut self, message: Message) {
match message { // проверка и запуск одного из методов
Message::Quit => self.quit(),
Message::Echo(x) => self.echo(x),
Message::ChangeColor(x, y, z) => self.change_color((x, y, z)),
Message::Move(x) => self.move_position(x),
} } }Использование Option как enum
fn divide(a: i32, b: i32) -> Option {
if b == 0 {
None
} else {
Some(a / b)
}}
fn main() {
match divide(10, 2) {
Some(result) => println!("Result: {}", result), // Result: 5
None => println!("Division by zero!"),
}}if let
В случае, когда выбор сводится к тому, что мы сравниваем 1 вариант с заданным паттерном и далее запускаем код при успехе, а в случае неравенства ничего не делаем, можно вместо match применять более короткую конструкцию if-let:
let config_max = Some(3u8);
match config_max {
Some(max) => println!("The maximum is configured to be {}", max),
_ => (), // другие варианты ничего не возвращают
}Превращается в:
let config_max = Some(3u8);
if let Some(max) = config_max {
println!("The maximum is configured to be {}", max);
}Применение if-let - это синтаксический сахар, укорачивает код, однако, лишает нас проверки наличия обработчиков на все варианты возвращаемых значений как в конструкции match.
Сравнение величин
Для сравнения значений в переменных есть метод std::cmp, который возвращает объект типа enum Ordering с вариантами:
use std::cmp::Ordering;
use std:io;
fn main() {
let secret_number = 42;
let mut guess = String::new();
io::stdin()
.read_line(&guess)
.expect("Read line failed!");
let guess :i32 = guess.trim().parse()
.expect("Error! Non-number value entered.");
match guess.cmp(&secret_number) {
Ordering::Greater => println!("Number too big"),
Ordering::Less => println!("Number too small"),
Ordering::Equal => println("Found exact number!")
}
}