到達点は、不可能な状態を作りにくい型を設計することです。前提は第05回です。
データの組と選択肢
structは複数の項目をまとめ、enumは複数の状態のいずれかを表します。失敗したかどうかのboolと、成功データと、エラーメッセージを独立に持つと、意味のない組合せが生まれます。状態ごとに必要なデータを持たせると整理できます。
#[derive(Debug)]
struct Lesson {
title: String,
minutes: u32,
}
impl Lesson {
fn new(title: &str, minutes: u32) -> Self {
Self { title: title.to_owned(), minutes }
}
fn description(&self) -> String {
format!("{}: {}分", self.title, self.minutes)
}
}
enum Progress {
NotStarted,
Studying(Lesson),
Completed { total_minutes: u32 },
}
fn describe(progress: &Progress) -> String {
match progress {
Progress::NotStarted => "未開始".to_owned(),
Progress::Studying(lesson) => lesson.description(),
Progress::Completed { total_minutes } => format!("完了: {total_minutes}分"),
}
}
fn main() {
let states = [Progress::NotStarted,
Progress::Studying(Lesson::new("enum", 25)),
Progress::Completed { total_minutes: 40 }];
for state in &states { println!("{}", describe(state)); }
}
src/main.rsの出力は未開始、enumの25分、完了40分です。implに関連する操作を置き、&self は読むだけの借用を表します。&mut self なら変更、selfなら所有値を受け取るという違いがあります。
網羅性を設計へ使う
enumへ新しい状態を追加すると、その状態を扱っていないmatchをコンパイラが指摘できます。何でもワイルドカードへ流すと、この助けが弱くなる場合があります。将来の追加を無視してよいか、必ず判断したいかで選びます。
一つの形だけを取り出したいならif let、早期終了と組み合わせるならlet elseも使えます。ただし最初はmatchで全状態を書き、何を省略してよいか確認する方が理解しやすくなります。
練習と解答
練習:Pausedという状態に中断理由を持たせ、describeも変更してください。
解答例:Paused { reason: String } を追加し、matchに対応する腕を書きます。理由が必要ない状態には理由フィールドが存在しないので、「未開始なのに中断理由だけある」という組合せを減らせます。
公式資料
Rust全20回の目次 | 前の回 | 次の回