例えば bool 型の引数を取る関数の可読性と保守性を高めることができる。
// 改善前
print_page(/* both_sides = */ true, /* color = */ false);// 改善後
enum Sides {
Both,
Single,
}
enum Output {
BlackAndWhite,
Color,
}
fn print_page(sides: Sides, output: Output) {
// ...
}
// このほうが型安全だし、可読性が高い
print_page(Sides::Both, Output::BlackAndWhite);newtype パターンを用いて bool をラップしても、同じように安全性と保守性が得られる。 引数が常に bool であることがわかっているなら newtype パターンを使ったほうが良い。 将来他の値をとる可能性があるなら enum を使う。
enum を 代数データ型 (ADT: Algebraic Data Type) として使うことができる。 この機能を用いると、プログラムのデータ構造の不変条件を Rust の型システムにエンコードできる。
// 望ましくない振る舞い
struct DisplayProps {
x: u32,
y: u32,
monochrome: bool,
// `monochrome` が真なら `fg_color` は (0, 0, 0) でなければならない
fg_color: RgbColor,
}// enum で置き換えるのに最適
enum Color {
Monochrome,
Foreground(RgbColor),
}
struct DisplayProps {
x: u32,
y: u32,
color: Color,
}- 存在しない可能性のある String は Option として表現する
""は存在しないのか空文字なのかが曖昧であるため
- 特になし
match ではなく、標準ライブラリが提供するさまざまな変換メソッドを使用すると、よりコンパクトで、定型的で、意図がはっきりとわかるコードになる。
値のみが重要で値がない場合は無視していい場合
// 改善前
struct S {
field: Option<i32>,
}
let s = S { field: Some(42) };
match &s.field {
Some(i) => println!("field is {i}"),
None => {}
}// 改善後
if let Some(i) = &s.field {
println!("field is {i}");
}失敗時に panic! を行うようにするとプログラムは即時に終了するが、残りのコードは成功したことを前提に書くことができる。これを明示的に match を用いて書くとコードが煩雑になる。
// 改善前
let result = std::fs::File::open("/etc/passwd");
let f = match result {
Ok(f) => f,
Err(_e) => panic!("Failed to open /etc/passwd!"),
}
// これ以降は、`f` が有効な `std::fs::File` であると想定// 改善後
let f = std::fs::File::open("/etc/passwd").unwrap();このヘルパ関数は panic! するので、これらを用いるのは panic! を選択するのと同じである。
上記の例のように、ファイルをオープンできなかったのは間違いなくエラーであるが、スライスが空の場合にスライスから最初の要素を取り出す first() メソッドが、失敗するのは本当のエラーではない。 このような場合は Option を返り値の型として用いることで表現したほうが良い。 Option と Result のいずれかを選択するには判断が必要だが、エラーに何か有用な情報があるのなら、Result を用いたほうが良い。
Result には #[must_use] 属性があり、コードが返された Result を無視した場合には、コンパイラが警告を発する。
// 明示的に match を用いればエラーを伝搬できるが、定型句をたくさん書かなければならない
fn find_user(username: &str) -> Result<UserId, std::io::Error> {
let f = match std::fs::File::open("/etc/passwd") {
Ok(f) => f,
Err(e) => return Err(From::from(e)),
};
// ...
}定型句を減らす鍵となるのが、Rust のクエスチョンマーク演算子?である。 これは、シンタックスシュガーで選択肢 Err へマッチングし、必要に応じてエラー型を変換し、return Err(...) 式を生成する。
// 改善後
fn find_user(username: &str) -> Result<UserId, std::io::Error> {
let f = std::fs::File::open("/etc/passwd")?;
// ...
}一般にはメソッド呼び出しのコストもかからない。これらはすべて #[inline] とマークされたジェネリック関数なので、コンパイルした結果生成される機械語は手で書いた場合のコードと全く同じになる。 以上より、「明示的な match 式ではなく Option と Result の変換を用いたほうが良い」。