Перечисления
Перечисления существуют во многих языках программирования, но их возможности различаются в каждом языке. Перечисления Rust аналогичны алгебраическим типам данных в функциональных языках, таких как F#, OCaml и Haskell. Функция match
Rust полезна в сочетании с перечислениями, что позволяет легко запускать разный код для разных значений перечисления.
Базовые перечисления
Давайте посмотрим, как мы можем расширить список моих домашних животных с помощью my dog, используя перечисления. Сначала создадим перечисление, представляющее виды домашних животных, которые у нас есть, и запишем нашу структуру в Pet, которая принимает вид домашнего животного в качестве перечисления.
enum PetKind {
Dog,
Cat,
}
struct Pet {
name: String,
color: String,
age: i32,
kind: PetKind,
}
Здесь мы определили два вида домашних животных: собаку и кошку. Теперь мы можем написать функцию, которая cможет вводить домашних животных и, наконец, создавать наших питомцев и выводить их всех на консоль.
#[derive(Debug)]
enum PetKind {
Dog,
Cat,
}
fn introduce_pet(pet: Pet) {
let kind = format!("{:?}", pet.kind);
println!("{} is a {} {}.", pet.name, pet.color, kind);
}
Обратите внимание, что для печати строки для нашего перечисления нам пришлось добавить атрибут #[derive(Debug)]
к нашему перечислению, чтобы сообщить компилятору сгенерировать некоторый дополнительный код, который позволяет преобразовать наше перечисление в строку с {:?}
.
Хорошо, итак, когда мы соберем все это вместе, мы сможем создать всех моих питомцев и использовать introduce_pet
, чтобы представить их.
enum PetKind {
Dog,
Cat,
}
struct Pet {
name: String,
color: String,
age: i32,
kind: PetKind,
}
fn main() {
let tarzan = Pet {
name: "Тарзан".to_string(),
color: "черная".to_string(),
age: 10,
kind: PetKind::Dog,
};
let cleo = Pet {
name: "Клео".to_string(),
color: "серая".to_string(),
age: 2,
kind: PetKind::Cat,
};
let jules = Pet {
name: "Джулия".to_string(),
color: "черная".to_string(),
age: 3,
kind: PetKind::Cat,
};
let toulouse = Pet {
name: "Тулуза".to_string(),
color: "рыжая".to_string(),
age: 4,
kind: PetKind::Cat,
};
introduce_pet(tarzan);
introduce_pet(cleo);
introduce_pet(jules);
introduce_pet(toulouse);
}
fn introduce_pet(pet: Pet) {
let kind = format!("{:?}", pet.kind);
println!("{} это {} {}.", pet.name, pet.color, kind);
}
Это приведет к следующему результату нашей программы.
Тарзан это черная Dog.
Клео это серая Cat.
Джулия это черная Cat.
Тулуза это рыжая Cat.
Что позволило нам расширить код, чтобы включить всех моих питомцев.
Это основной вариант использования для большинства типов перечислений на разных языках, но перечисления Rust делают много чего и могут даже использоваться для хранения значений. Давайте узнаем об этом больше.
Перечисления Rust
В Rust мы можем присваивать некоторые значения нашим перечислениям, что является мощным в сочетании с синтаксисом сопоставления с образцом. Представьте, что мы пишем систему HTTP-маршрутизатора, использующую функции.
enum HttpRequest {
Get(String),
Post { url: String, body: String},
Put {url: String, body: String},
Patch { url: String, body: String},
Delete {url: String, body: String},
}
fn main() {
let request = imaginary_request();
request_handler(request);
}
fn request_handler(request: HttpRequest) {
match request {
HttpRequest::Get(url) => println!("GET {}", url),
HttpRequest::Post { url, body } => println!("POST {} {}", url, body),
HttpRequest::Put { url, body } => println!("PUT {} {}", url, body),
HttpRequest::Patch { url, body } => println!("PATCH {} {}", url, body),
HttpRequest::Delete { url, body } => println!("DELETE {} {}", url, body),
}
}
fn imaginary_request() -> HttpRequest {
HttpRequest::Get("https://www.rust-lang.org".to_string())
}
Для простоты я использую только строки, но с этим типом перечисления поддерживается любое значение. Мы можем передавать значения с помощью нашего перечисления и использовать синтаксис сопоставления с образцом с ключевым словом match
для выполнения другого кода на основе полученных входных данных. Подобно структурам в Rust, мы можем не присваивать значений, как в случае с Unit-структурами с неназванным значениям, используя синтаксис Tuple Struct
, и именованным значениям, используя классический синтаксис C Struct
.
Существует множество перечислений, уже предоставленных стандартной библиотекой Rust. Тот, который, в частности, я хотел бы объяснить. Я сделаю это, написав код, который реализует подбрасывание монеты, которая не всегда падает на одну сторону.
use rand::Rng;
enum Coin {
Heads,
Tails,
}
fn main() {
let coin = flip_coin();
match coin {
Some(Coin::Heads) => println!("Орел!"),
Some(Coin::Tails) => println!("Орешка!"),
None => println!("Монета приземлилась на ее край!"),
}
}
fn flip_coin() -> Option<Coin> {
// Иногда монета падает на край, и мы ничего не получаем...
let mut rng = rand::thread_rng();
let coin = rng.gen_range(0..3);
match coin {
0 => Some(Coin::Heads),
1 => Some(Coin::Tails),
_ => None,
}
}
Во-первых, я реализовал перечисление для нашей монеты, в котором есть орел и решка. Но в нашей программе подбрасывание монеты может быть нерешенным, когда монета падает на ребро. Вместо того, чтобы расширять наше перечисление с помощью этого варианта использования, мы можем использовать встроенную опцию enum<T>
, которая позволяет нам возвращать Some(T)
или None
.
Это полезно для функций, которые не всегда возвращают значение, после чего мы можем использовать выражение соответствия для обработки возможных сценариев.
Упражнения
enums1.rs
#[derive(Debug)]
enum Message {
// TODO: определить несколько типов сообщений, для использования ниже
}
fn main() {
println!("{:?}", Message::Quit);
println!("{:?}", Message::Echo);
println!("{:?}", Message::Move);
println!("{:?}", Message::ChangeColor);
}
Все, что нам нужно сделать, это реализовать различные типы сообщений, и мы сможем перейти к следующему упражнению.
enum Message {
Quit,
Echo,
Move,
ChangeColor,
}
enums2.rs
#[derive(Debug)]
enum Message {
// TODO: определите различные варианты, используемые ниже
}
impl Message {
fn call(&self) {
println!("{:?}", self);
}
}
fn main() {
let messages = [
Message::Move { x: 10, y: 30 },
Message::Echo(String::from("Привет мир")),
Message::ChangeColor(200, 255, 255),
Message::Quit,
];
for message in &messages {
message.call();
}
}
В этом упражнении будут использоваться все различные типы возможных реализаций для перечислений. Для Move
мы будем использовать классический синтаксис структуры C
, для Echo
и changeColor
мы будем использовать синтаксис структуры Tuple
, а для Quit
- синтаксис Unit Struct
.
enum Message {
Move{x: i32, y: i32},
Echo(String),
ChangeColor(u8, u8, u8),
Quit,
}
enums3.rs
enum Message {
// TODO: реализовать типы вариантов сообщений на основе их использования ниже
}
struct Point {
x: u8,
y: u8,
}
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) {
// TODO: создать выражение соответствия для обработки различных вариантов сообщения
// Remember: При передаче кортежа в качестве аргумента функции потребуются дополнительные скобки: fn function((t, u, p, l, e))
}
}
Давайте начнем с реализации перечисления сообщений, просмотрев функции, определенные в State
. Мы можем видеть, что change_color
использует кортеж с (u8, u8, u8)
, quit
не принимает параметры, echo
принимает строку, а move_position
принимает точку. Давайте начнем с реализации перечисления с этими значениями.
enum Message {
ChangeColor(u8, u8, u8),
Quit,
Echo(String),
Move(Point),
}
Теперь мы хотим использовать выражение сопоставления с синтаксисом сопоставления с образцом для обработки всех сообщений.
fn process(&mut self, message: Message) {
match message {
Message::ChangeColor(r, g, b) => self.change_color((r, g, b)),
Message::Quit => self.quit(),
Message::Echo(s) => self.echo(s),
Message::Move(p) => self.move_position(p),
}
}
Это решает наше последнее упражнение для перечислений. Мы узнали, что перечисления могут содержать значения в Rust, подобные структурам, и что мы можем писать выражения соответствия для обработки различных значений для перечислений.