`if` 式

Essential
最終更新: タグ: Rust

前提知識

Rustのifは単なる条件文ではなく、値を生成するだ。このひとつの性質が、代入・関数本体・条件ロジックの書き方をすべて変える。

基本的な形

条件式はboolでなければならない。Rustは整数やポインタなどを自動でブール値に変換しないため、nが整数のときif nはコンパイルエラーになる——if n != 0と書くこと。

if temperature > 100.0 {
    println!("too hot");
} else if temperature < 0.0 {
    println!("too cold");
} else {
    println!("just right");
}

値を生成する式としての if

ifは式なので、その結果を束縛に直接代入できる:

let label = if n > 0 { "positive" } else { "non-positive" };

すべての分岐は同じ型を生成しなければならない。型が一致しない分岐はコンパイルエラーになる:

let x = if flag { 1 } else { "text" }; // error: expected integer, found `&str`

各分岐はブロックであり、ブロックの値は最後の式(末尾にセミコロンなし)だ。末尾にセミコロンを付けると式が文になり、ブロックは()を返す。宣言された型が()でない場合は型エラーになる:

let x = if flag { 1 } else { 2; }; // error: expected i32, found ()
//                             ^ セミコロンが 2 を捨てる

else の省略

ifとして使う場合——その値が捨てられる場合——else分岐を省略できる。if分岐と暗黙のelse省略の両方が()を返すので、型が一致する:

if verbose {
    println!("debug: starting");
}

このような形を式として使おうとすると、コンパイラはelseを要求する:

let x = if flag { 1 }; // error: `if` may be missing an `else` clause

早期リターン — ガード節

Rustの関数ではreturnは早期終了にのみ使う。よく使われるパターンがガード節(guard clause)だ:先頭で事前条件を確認し、条件を満たさない場合は即座に抜ける:

fn parse_positive(s: &str) -> Option<u32> {
    if s.is_empty() {
        return None;
    }
    let n: u32 = s.parse().ok()?;
    if n == 0 {
        return None;
    }
    Some(n)
}

ガード節はハッピーパスのネストを浅く保ち、読みやすくする。ループ内では同じイディオムでreturnの代わりにcontinue(現在のイテレーションをスキップ)やbreak(ループを抜ける)を使う。

if let

if letはパターンと条件を組み合わせる:パターンがマッチしたときだけブロックが実行される。

let config: Option<&str> = Some("debug");

if let Some(level) = config {
    println!("log level: {level}");
}

これは意味のあるアームが一つのmatchの短縮形だ。elseを追加してマッチしない場合を処理することもできる:

if let Some(level) = config {
    println!("log level: {level}");
} else {
    println!("using default log level");
}

if letも通常のifと同じく式なので、両分岐の型が一致していれば結果を束縛に代入できる。if letOptionResult・ペイロードを持つ列挙型など、特定の一つのバリアントだけに関心がある状況で頻繁に使われる。パターン全般についてはパターンマッチングで扱う。