1.4 Состояния
Опытный мастер никогда не подписывает инструменты “первый” и “второй”. Начинающий подписывает - и ему это кажется достаточным. Ему кажется достаточным ровно до того момента, когда инструментов становится три, или когда он возвращается спустя месяц, или когда приходит кто-то ещё. Одно из этих случается всегда. Статус задачи сейчас именно такой.
Попытка
После прошлой главы main.rs печатает статус так:
println!("Done: {}", task.status);
Это выведет на печать true или false. Неплохо, но хочется большего: задача должна уметь менять
состояние. Добавим строку после инициализации:
task.status = true;
Компилятор подсказывает:
error[E0594]: cannot assign to `task.status`, as `task` is not declared as mutable
--> src/main.rs:XX:XX
|
XX | task.status = true;
| ^^^^^^^^^^^^^^^^^^ cannot assign
|
help: consider changing this to be mutable
|
XX | let mut task = Task {
| +++
В Rust, по умолчанию, значения нельзя изменять, получить такое право можно лишь явно - let mut:
let mut task = Task {
id: 1,
title: String::from("Buy coffee"),
status: false,
};
Теперь компилятор примет строку task.status = true. Но true - имя значения булева типа, а не имя
состояния задачи. С первого взгляда разница невелика - но принципиальна на практике.
Имена
Для именованных состояний в Rust есть тип enum, где можно определить строгий набор вариантов. Добавим его в начало файла,
до struct Task. #[derive(Debug)] нужен сразу: компилятор не может сгенерировать Debug
для структуры, если хотя бы одно её поле Debug не реализует, - а Task скоро получит
поле типа Status:
#[derive(Debug)]
enum Status {
Todo,
Done,
}
Это не два значения одного типа - это два отдельных варианта с собственными именами.
Заменим bool в структуре и скорректируем main:
struct Task {
id: u64,
title: String,
status: Status, // было: bool
}
// в main():
let mut task = Task {
id: 1,
title: String::from("Buy coffee"),
status: Status::Todo, // было: false
};
// ...
task.status = Status::Done; // было: true
Уберём println!("Done: {}", task.status) - Status не реализует Display, эта строка
больше не скомпилируется. Вместо неё поставим match. Попробуем с одним вариантом:
match task.status {
Status::Todo => println!("todo"),
}
Заклинание
Компилятор подсказывает недочёт:
error[E0004]: non-exhaustive patterns: `Done` not covered
--> src/main.rs:XX:XX
|
XX | match task.status {
| ^^^^^^^^^^^ pattern `Done` not covered
|
note: `Status` defined here
--> src/main.rs:XX:XX
|
XX | enum Status {
| ------
XX | Todo,
XX | Done,
| ^^^^ not covered
= note: the matched value is of type `Status`
match в Rust требует охватить все возможные варианты. Это гарантия: если Status
когда-нибудь получит новый вариант, компилятор найдёт каждый match, который про него
не знает, и откажется компилировать. Забытый вариант не проскользнёт молча.
Добавляем недостающий вариант и смотрим на переход:
#[derive(Debug)]
enum Status {
Todo,
Done,
}
#[derive(Debug)]
struct Task {
id: u64,
title: String,
status: Status,
}
fn main() {
let mut task = Task {
id: 1,
title: String::from("Buy coffee"),
status: Status::Todo,
};
println!("Task #{}: {}", task.id, task.title);
println!("{:#?}", task);
task.status = Status::Done;
println!("{:#?}", task);
match task.status {
Status::Todo => println!("todo"),
Status::Done => println!("done"),
}
}
Task #1: Buy coffee
Task {
id: 1,
title: "Buy coffee",
status: Todo,
}
Task {
id: 1,
title: "Buy coffee",
status: Done,
}
done
Первый {:#?} - задача до изменения. Второй - после. match подтверждает новое состояние.
make ci проходит.
Полный код
tqдля этой главы -1-a-task/04-states-of-being/.
Lore: _ в match
Иногда хочется обработать один вариант, а остальные проигнорировать. Для этого есть _ -
паттерн, совпадающий с чем угодно:
match task.status {
Status::Done => println!("done"),
_ => {}
}
Это работает - но для Status это плохая идея. Смысл исчерпывающего match именно в том,
чтобы компилятор предупреждал при добавлении нового варианта. _ эту защиту убирает:
появится InProgress - и он молча попадёт под _ без единого предупреждения.
_ уместен, когда вариантов действительно много и большинство обрабатываются одинаково,
или когда enum чужой и его нельзя контролировать. Для собственного Status лучше всегда
писать все варианты явно.