🔰

Rust をはじめるメンバーに伝えたい「 if より match 」

に公開

Rust をはじめるメンバーに伝えたい「 if より match 」

ぼくの所属するドクターメイト株式会社では 2023-07 から Rust を使用しています。 Rust の使用を決定した当時から現在に至るまで「メンバー全員が Rust についての知識・経験が豊富だった」なんてことはありません。常に不慣れなメンバーが居て、慣れたメンバーもより良いコードを書くために試行錯誤を繰り返しています。

「 if より match 」は、そんな新しく Rust をはじめるメンバーに伝えたいことのひとつです。

if を選択しがち

チームにジョインするメンバーは大抵の場合いくつかのプログラミング言語を経験しており、基本的なコーディングはできます。ただ、代数的データ型やパターンマッチに触れる機会に乏しかった場合、条件分岐のために if を第一に選択する傾向があります。

// if で条件分岐をする例
let x = 0;
if x == 0 {
    println!("zero but true");
} else {
    println!("non-zero but false");
}

このコードは期待通りに動作します。

しかし、あえて「 if より match 」の言葉に従い、 match を優先して検討すると良いです。

// match で条件分岐をする例
let x = 0;
match x == 0 {
    true => println!("zero but true"),
    false => println!("non-zero but false"),
}

この例は過剰で、 if で十分です。

ただ、意識的に match を検討する習慣から得られるものもあります。

match で条件の考慮漏れに気づける

たとえば、条件の考慮漏れに気づけることがあります。

// 複数の条件をひとつの match で表現する例
for i in 1..=100 {
    match (i % 3 == 0, i % 5 == 0) {
        (false, false) => println!("{i}"),
        (false, true) => println!("Buzz"),
        (true, false) => println!("Fizz"),
        (true, true) => println!("Fizz Buzz"),
    }
}

これを if ... else if ... else ... と記述していたら、考慮漏れしている分岐には気づきにくく (true, true) のケースが落とし穴になることもあるでしょう。 (まさに Fizz Buzz はこの落とし穴の考慮を問うための問題ですね)

match で代数的データ型の活用につながる

ほかにも、 boolenum Bool { False, True } とほとんど同じであることに気づければ、代数的データ型の活用への道が開けます。

// bool 的なデータ型の例
enum Bool {
    False,
    True,
}

let is_x = Bool::False;
match is_x {
    Bool::False => println!("False is falsy?"),
    Bool::True => println!("True is truthy?"),
}

また過剰な例で、これなら bool で十分です。

でも、大切なのは match なら bool にしばられる必要はないということです。

// bool に縛られない switch のような分岐の例
enum Fruit {
    Apple,
    Banana,
    Cucumber,
}

let fruit = Fruit::Apple;
match fruit {
    Fruit::Apple => println!("Apple is fruit"),
    Fruit::Banana => println!("Banana is fruit"),
    Fruit::Cucumber => println!("Cucumber is fruit?"),
}

この例だと「他のプログラミング言語の switch みたいなものかー」という理解になってしまいますが、それぞれに異なる値を持てることに気づけば代数的データ型の表現力に触れることができます。

// variant ごとに異なる値を持つ例
enum Shape {
    Circle { r: f64 },
    Rectangle { h: f64, w: f64 }
}

let shape = Shape::Circle { r: 2.0 };
let area = match shape {
    Shape::Circle { r } => r * r * std::f64::consts::PI,
    Shape::Rectangle { h, w } => h * w
};
println!("{area}");

OptionResult もこの流れの延長で出てきますし、データ構造で制約を表現する考えにも近づいていきます。

まとめ

「 if よりも match 」を考えることで、条件分岐での考慮漏れを減らしたり、代数的データ型とパターンマッチの組み合わせに慣れることができます。

if matches!(...)if let ... のようなものはあるのですが、他のプログラミング言語から Rust に入ったときの最初の考え方として「 if より match 」を伝えるのは良いことだとぼくは考えています。

参考

GitHubで編集を提案
ドクターメイト

Discussion