Rust from Basics to Mastery — Part 4
Expressing valid states with structures and enums
The articles written on this blog were created with the goal of teaching. Also, the information in the article may be incorrect, so please be careful.
Structures group related data, and enums list possible states. If you use the two together, you do not have to forcefully express combinations that cannot exist with fields and bools.
Structure and impl
struct Player {
name: String,
health: i32,
}
impl Player {
fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
health: 100,
}
}
fn take_damage(&mut self, amount: i32) {
self.health = (self.health - amount.max(0)).max(0);
}
}
The impl block defines creation functions and methods related to the structure. &mut self means that the method borrows exclusively to modify the object.
Expressing state with enum
enum QuestState {
Locked,
Active { progress: u32, goal: u32 },
Completed { reward_claimed: bool },
}
There is progress only when Active, and reward receipt only when Completed. Invalid combinations are reduced compared to expressing the same information with multiple bool and nullable fields.
pattern matching
fn describe(state: &QuestState) -> String {
match state {
QuestState::Locked => String::from("Locked"),
QuestState::Active { progress, goal } => {
format!("{progress}/{goal}")
}
QuestState::Completed { reward_claimed: true } => {
String::from("Reward claimed")
}
QuestState::Completed { reward_claimed: false } => {
String::from("Reward ready")
}
}
}
match checks whether all cases of the enum have been processed. Adding a new state will cause unprocessed code to appear as compilation errors.
Option and Result are also the same enum. In Rust, states and failure possibilities are expressed as types, and each case is handled using matches or dedicated methods.