Перечисления в Rust
Перечисления в Rust не так просты, как это понятие в других языках программирования, но их всё ещё можно использовать очень просто:
Пример
enum Book {
Papery, Electronic
}
fn main() {
let book = Book::Papery;
println!("{:?}", book);
}
Вывод:
Papery
Книги делятся на бумажные книги (Papery book) и электронные книги (Electronic book).
Если вы сейчас разрабатываете систему управления библиотекой и вам нужно описать различные атрибуты двух типов книг (у бумажных книг есть шифр, у электронных книг есть только URL), вы можете добавить описания атрибутов в виде кортежей к членам перечисления:
enum Book {
Papery(u32),
Electronic(String),
}
let book = Book::Papery(1001);
let ebook = Book::Electronic(String::from("url://..."));
Если вы хотите дать имена атрибутам, можно использовать синтаксис struct:
enum Book {
Papery { index: u32 },
Electronic { url: String },
}
let book = Book::Papery{index: 1001};Хотя вы можете назвать их таким образом, учтите, что нельзя получать доступ к атрибутам, привязанным к варианту enum, так же, как к полям struct. Способ доступа к ним — в синтаксисе match.
Синтаксис match
Назначение enum — классифицировать определённый тип вещей, а назначение классификации — описывать разные ситуации. Исходя из этого принципа, типы enum часто в конечном итоге обрабатываются структурами ветвления (switch во многих языках). Синтаксис switch классический, но в Rust он не поддерживается. Причина, по которой многие языки отказываются от switch, в том, что switch подвержен проблемам fall-through, вызванным забытым break. Языки вроде Java и C# предотвращают эту ситуацию с помощью проверок безопасности.
Rust реализует структуры ветвления с помощью оператора match. Давайте сначала посмотрим, как использовать match для обработки enum:
Пример
enum Book {
Papery {index: u32},
Electronic {url: String},
}
let book = Book::Papery{index: 1001};
let ebook = Book::Electronic{url: String::from("url...")};
match book {
Book::Papery { index } => {
println!("Бумажная книга {}", index);
},
Book::Electronic { url } => {
println!("Электронная книга {}", url);
}
}
}
Вывод:
Papery book 1001
Блок match также можно рассматривать как функциональное выражение, и он может иметь возвращаемое значение:
match 枚举类实例 {
分类1 => 返回值表达式,
分类2 => 返回值表达式,
...
}Но типы всех выражений, возвращающих значение, должны быть одинаковыми!
Если дополнительные атрибуты enum определены как кортеж, нужно временно указать имя в блоке match:
Пример
Papery(u32),
Electronic {url: String},
}
let book = Book::Papery(1001);
match book {
Book::Papery(i) => {
println!("{}", i);
},
Book::Electronic { url } => {
println!("{}", url);
}
}
Помимо ветвления по enum, match также может ветвиться по данным таких типов, как целые числа, числа с плавающей точкой, символы и ссылки на срезы строк (&str). Среди них ветвление по числам с плавающей точкой допустимо, но не рекомендуется, поскольку проблемы с точностью могут привести к ошибкам ветвления.
При ветвлении по типам, не являющимся enum, необходимо обработать всеохватывающий случай, даже если в этом всеохватывающем случае ничего не нужно делать..Всеохватывающий случай использует подчёркивание_для обозначения:
Пример
let t = "abc";
совпадение t {
"abc" => println!("Yes"),
_ => {},
}
}
Перечисление Option
Option — это перечисление в стандартной библиотеке Rust. Этот тип используется для заполнения пробела, связанного с тем, что Rust не поддерживает null-ссылки.
Многие языки поддерживают существование null (C/C++, Java), что удобно, но это также создаёт огромные проблемы. Изобретатель null также признал это: «удобная идея привела к совокупным потерям в один миллиард долларов».
null часто наносит программе фатальный удар, когда разработчики считают, что всё не null: ведь как только возникает одна такая ошибка, выполнение программы должно полностью завершиться.
Чтобы решить эту проблему, многие языки по умолчанию не допускают null, но поддерживают появление null на уровне языка (часто помечается символом ? перед типом).
Java по умолчанию поддерживает null, но может ограничивать появление null с помощью аннотации @NotNull. Это способ справиться.
Rust полностью запрещает null-значения на уровне языка, но, к сожалению, null может эффективно решать небольшое число задач, поэтому Rust ввёл перечисление Option:
enum Option<T> {
Some(T),
None,
}Если вы хотите определить тип, который может быть null, вы можете сделать так:
let opt = Option::Some("Hello");Если вы хотите выполнить некоторые операции над opt, вы должны сначала определить, является ли онOption::None:
Пример
let opt = Option::Some("Hello");
match opt {
Option::Some(something) => {
println!("{}", something);
},
Option::None => {
println!("opt — это ничто");
}
}
}
Вывод:
Hello
Если ваша переменная изначально имеет значение null, подумайте о компиляторе: как он узнаёт, какого типа переменная, когда значение не null?
Следовательно, Option с начальным значением null должен иметь явный тип:
Пример
let opt: Option<&str> = Option::None;
match opt {
Option::Some(something) => {
println!("{}", something);
},
Option::None => {
println!("opt — это ничто");
}
}
}
Вывод:
opt is nothing
Такая конструкция усложняет программирование с null-значениями, но именно это и нужно для создания стабильной и эффективной системы. Поскольку Option по умолчанию введён в компиляторе Rust, при его использовании можно опустить Option:: и напрямую писать None или Some().
Option — это особый тип enum, который может содержать значения для выбора ветви:
Пример
let t = Some(64);
совпадение t {
Some(64) => println!("Да"),
_ => println!("Нет"),
}
}
Синтаксис if let
Пример
совпадение i {
0 => println!("zero"),
_ => {},
}
Поместите это в функцию main, вывод будет:
zero
Цель этой программы — определить, является ли i числом 0, и если да, вывести zero.
Теперь используйте синтаксис if let, чтобы сократить этот код:
let i = 0;
if let 0 = i {
println!("zero");
}Синтаксис if let выглядит следующим образом:
if let 匹配值 = 源变量 {
语句块
}После этого можно добавить блок else, чтобы обработать все остальные случаи.
Синтаксис if let можно считать «синтаксическим сахаром» для оператора match, который различает только два случая (синтаксический сахар — это удобная альтернатива с тем же основным принципом, что и у некоторого синтаксиса).
Это также применимо к перечислениям:
Пример
enum Book {
Papery(u32),
Electronic(String)
}
let book = Book::Electronic(String::from("url"));
if let Book::Papery(index) = book {
println!("Papery {}", index);
} else {
println!("Not papery book");
}
}