1.4 States of Being
An experienced craftsman never labels tools “first” and “second.” A beginner does - and it seems like enough. It seems like enough right up until there are three tools, or until they come back a month later, or until someone else arrives. One of these always happens. The task’s status is exactly that right now.
The Attempt
After the previous chapter, main.rs prints the status like this:
println!("Done: {}", task.status);
That will print true or false. Not bad, but more is needed: a task should be able to
change state. Add a line after the initialisation:
task.status = true;
The compiler responds:
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 {
| +++
In Rust, values cannot be changed by default - that right must be claimed explicitly with
let mut:
let mut task = Task {
id: 1,
title: String::from("Buy coffee"),
status: false,
};
Now the compiler will accept task.status = true. But true is the name of a boolean
value, not the name of a task state. The difference seems small at first - and matters in
practice.
The Names
For named states, Rust has the enum type, where a strict set of variants can be defined.
Add it at the top of the file, before struct Task. #[derive(Debug)] is needed right
away: the compiler cannot generate Debug for a struct if any of its fields do not
implement Debug - and Task is about to gain a field of type Status:
#[derive(Debug)]
enum Status {
Todo,
Done,
}
These are not two values of the same type - they are two distinct variants with their own
names. Replace bool in the struct and update main:
struct Task {
id: u64,
title: String,
status: Status, // was: bool
}
// in main():
let mut task = Task {
id: 1,
title: String::from("Buy coffee"),
status: Status::Todo, // was: false
};
// ...
task.status = Status::Done; // was: true
Remove println!("Done: {}", task.status) - Status does not implement Display, so
that line will no longer compile. Replace it with a match. Try it with one variant:
match task.status {
Status::Todo => println!("todo"),
}
The Enchantment
The compiler points out the problem:
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 in Rust requires every possible variant to be covered. This is a guarantee: if
Status ever gains a new variant, the compiler will find every match that does not know
about it and refuse to compile. A forgotten variant cannot slip through silently.
Add the missing variant and watch the transition:
#[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
The first {:#?} is the task before the change. The second is after. The match confirms
the new state. make ci passes.
The complete
tqcode for this chapter is in1-a-task/04-states-of-being/.
Lore: _ in match
Sometimes you want to handle one variant and ignore the rest. For this there is _ - a
pattern that matches anything:
match task.status {
Status::Done => println!("done"),
_ => {}
}
This works - but for Status it is a bad idea. The point of an exhaustive match is
precisely that the compiler warns you when a new variant is added. _ removes that
protection: add InProgress and it silently falls under _ without a single warning.
_ is appropriate when there are genuinely many variants and most are handled the same way,
or when the enum belongs to someone else and cannot be controlled. For your own Status,
it is better to always write every variant explicitly.