制御フロー

条件が真かどうかによってコードを走らせるかどうかを決定したり、 条件が真の間繰り返しコードを走らせるか決定したりすることは、多くのプログラミング言語において、基本的な構成ブロックです。 Rustコードの実行フローを制御する最も一般的な文法要素は、if式とループです。

if

if式によって、条件に依存して枝分かれをさせることができます。条件を与え、以下のように宣言します。 「もし条件が合ったら、この一連のコードを実行しろ。条件に合わなければ、この一連のコードは実行するな」と。

projectsディレクトリにbranchesという名のプロジェクトを作ってif式について掘り下げていきましょう。 src/main.rsファイルに、以下のように入力してください:

ファイル名: src/main.rs

fn main() {
    let number = 3;

    if number < 5 {
        println!("condition was true");       // 条件は真でした
    } else {
        println!("condition was false");      // 条件は偽でした
    }
}

if式は全て、キーワードのifから始め、条件式を続けます。今回の場合、 条件式は変数numberが5未満の値になっているかどうかをチェックします。 条件が真の時に実行したい一連のコードを条件式の直後に波かっこで包んで配置します。if式の条件式と紐付けられる一連のコードは、 時としてアームと呼ばれることがあります。 第2章の「予想と秘密の数字を比較する」の節で議論したmatch式のアームと同じです。

オプションとして、else式を含むこともでき(ここではそうしています)、これによりプログラムは、 条件式が偽になった時に実行するコードを与えられることになります。仮に、else式を与えずに条件式が偽になったら、 プログラムは単にifブロックを飛ばして次のコードを実行しにいきます。

このコードを走らせてみましょう; 以下のような出力を目の当たりにするはずです:

$ cargo run
   Compiling branches v0.1.0 (file:///projects/branches)
    Finished dev [unoptimized + debuginfo] target(s) in 0.31s
     Running `target/debug/branches`
condition was true

numberの値を条件がfalseになるような値に変更してどうなるか確かめてみましょう:

fn main() {
    let number = 7;

    if number < 5 {
        println!("condition was true");
    } else {
        println!("condition was false");
    }
}

再度プログラムを実行して、出力に注目してください:

$ cargo run
   Compiling branches v0.1.0 (file:///projects/branches)
    Finished dev [unoptimized + debuginfo] target(s) in 0.31s
     Running `target/debug/branches`
condition was false

このコード内の条件式は、bool型でなければならないことにも触れる価値があります。 条件式が、bool型でない時は、エラーになります。例えば、試しに以下のコードを実行してみてください:

ファイル名: src/main.rs

fn main() {
    let number = 3;

    if number {
        println!("number was three");     // 数値は3です
    }
}

今回、ifの条件式は3という値に評価され、コンパイラがエラーを投げます:

$ cargo run
   Compiling branches v0.1.0 (file:///projects/branches)
error[E0308]: mismatched types
              (型が合いません)
 --> src/main.rs:4:8
  |
4 |     if number {
  |        ^^^^^^ expected `bool`, found integer
  |               (bool型を予期したのに、整数変数が見つかりました)

For more information about this error, try `rustc --explain E0308`.
error: could not compile `branches` due to previous error

このエラーは、コンパイラはbool型を予期していたのに、整数だったことを示唆しています。 RubyやJavaScriptなどの言語とは異なり、Rustでは、論理値以外の値が、自動的に論理値に変換されることはありません。 明示し、必ずifには条件式として、論理値を与えなければなりません。 例えば、数値が0以外の時だけifのコードを走らせたいなら、以下のようにif式を変更することができます:

ファイル名: src/main.rs

fn main() {
    let number = 3;

    if number != 0 {
        println!("number was something other than zero");   // 数値は0以外の何かです
    }
}

このコードを実行したら、number was something other than zeroと表示されるでしょう。

else ifで複数の条件を扱う

ifelseを組み合わせてelse if式にすることで複数の条件を持たせることもできます。例です:

ファイル名: src/main.rs

fn main() {
    let number = 6;

    if number % 4 == 0 {
        // 数値は4で割り切れます
        println!("number is divisible by 4");
    } else if number % 3 == 0 {
        // 数値は3で割り切れます
        println!("number is divisible by 3");
    } else if number % 2 == 0 {
        // 数値は2で割り切れます
        println!("number is divisible by 2");
    } else {
        // 数値は4、3、2で割り切れません
        println!("number is not divisible by 4, 3, or 2");
    }
}

このプログラムには、通り道が4つあります。実行後、以下のような出力を目の当たりにするはずです:

$ cargo run
   Compiling branches v0.1.0 (file:///projects/branches)
    Finished dev [unoptimized + debuginfo] target(s) in 0.31s
     Running `target/debug/branches`
number is divisible by 3

このプログラムを実行すると、if式が順番に吟味され、最初に条件が真になった本体が実行されます。 6は2で割り切れるものの、number is devisible by 2や、 elseブロックのnumber is not divisible by 4, 3, or 2という出力はされないことに注目してください。 それは、Rustが最初の真条件のブロックのみを実行し、 条件に合ったものが見つかったら、残りはチェックすらしないからです。

else if式を使いすぎると、コードがめちゃくちゃになってしまうので、1つ以上あるなら、 コードをリファクタリングしたくなるかもしれません。これらのケースに有用なmatchと呼ばれる、 強力なRustの枝分かれ文法要素については第6章で解説します。

let文内でif式を使う

ifは式なので、let文の右辺に持ってくることができます。リスト3-2のようにですね。

ファイル名: src/main.rs

fn main() {
    let condition = true;
    let number = if condition { 5 } else { 6 };

    // numberの値は、{}です
    println!("The value of number is: {}", number);
}

リスト3-2: if式の結果を変数に代入する

このnumber変数は、if式の結果に基づいた値に束縛されます。このコードを走らせてどうなるか確かめてください:

$ cargo run
   Compiling branches v0.1.0 (file:///projects/branches)
    Finished dev [unoptimized + debuginfo] target(s) in 0.30s
     Running `target/debug/branches`
The value of number is: 5

一連のコードは、そのうちの最後の式に評価され、数値はそれ単独でも式になることを思い出してください。 今回の場合、このif式全体の値は、どのブロックのコードが実行されるかに基づきます。これはつまり、 ifの各アームの結果になる可能性がある値は、同じ型でなければならないということになります; リスト3-2で、ifアームもelseアームも結果は、i32の整数でした。以下の例のように、 型が合わない時には、エラーになるでしょう:

ファイル名: src/main.rs

fn main() {
    let condition = true;

    let number = if condition { 5 } else { "six" };

    println!("The value of number is: {}", number);
}

このコードをコンパイルしようとすると、エラーになります。ifelseアームは互換性のない値の型になり、 コンパイラがプログラム内で問題の見つかった箇所をズバリ指摘してくれます:

$ cargo run
   Compiling branches v0.1.0 (file:///projects/branches)
error[E0308]: `if` and `else` have incompatible types
              (ifとelseの型に互換性がありません)
 --> src/main.rs:4:44
  |
4 |     let number = if condition { 5 } else { "six" };
  |                                 -          ^^^^^ expected integer, found `&str`
  |                                                 (整数変数を予期しましたが、&strが見つかりました)
  |                                 |
  |                                 expected because of this

For more information about this error, try `rustc --explain E0308`.
error: could not compile `branches` due to previous error

ifブロックの式は整数に評価され、elseブロックの式は文字列に評価されます。これでは動作しません。 変数は単独の型でなければならないからです。コンパイラは、コンパイル時にnumber変数の型を確実に把握する必要があるため、 コンパイル時にnumberが使われている箇所全部で型が有効かどうか検査することができるのです。 numberの型が実行時にしか決まらないのであれば、コンパイラはそれを実行することができなくなってしまいます; どの変数に対しても、架空の複数の型があることを追いかけなければならないのであれば、コンパイラはより複雑になり、 コードに対して行える保証が少なくなってしまうでしょう。

ループでの繰り返し

一連のコードを1回以上実行できると、しばしば役に立ちます。この作業用に、 Rustにはいくつかのループが用意されています。ループは、本体内のコードを最後まで実行し、 直後にまた最初から処理を開始します。 ループを試してみるのに、loopsという名の新プロジェクトを作りましょう。

Rustには3種類のループが存在します: loopwhileforです。それぞれ試してみましょう。

loopでコードを繰り返す

loopキーワードを使用すると、同じコードを何回も何回も永遠に、明示的にやめさせるまで実行します。

例として、loopsディレクトリのsrc/main.rsファイルを以下のような感じに書き換えてください:

ファイル名: src/main.rs

fn main() {
    loop {
        println!("again!");   // また
    }
}

このプログラムを実行すると、プログラムを手動で止めるまで、何度も何度も続けてagain!と出力するでしょう。 ほとんどの端末でctrl-cというショートカットが使え、 永久ループに囚われてしまったプログラムを終了させられます。試しにやってみましょう:

$ cargo run
   Compiling loops v0.1.0 (file:///projects/loops)
    Finished dev [unoptimized + debuginfo] target(s) in 0.29 secs
     Running `target/debug/loops`
again!
again!
again!
again!
^Cagain!

^Cという記号が出た場所が、ctrl-cを押した場所です。^Cの後にはagain!と表示されたり、 されなかったりします。ストップシグナルをコードが受け取った時にループのどこにいたかによります。

幸いなことに、Rustにはループを抜け出す別のより信頼できる手段があります。 ループ内にbreakキーワードを配置することで、プログラムに実行を終了すべきタイミングを教えることができます。 第2章の「正しい予想をした後に終了する」節の数当てゲーム内でこれをして、ユーザが予想を的中させ、 ゲームに勝った時にプログラムを終了させたことを思い出してください。

数当てゲームでcontinueを使用しました。continueはループの中で残っているコードをスキップして次のループに移るためのものです。

ループ内にループがある場合、breakcontinueは最も内側のループに適用されます。 ループラベルを使用することで、breakcontinueが適用されるループを指定することができます。 以下に例を示します。

fn main() {
    let mut count = 0;
    'counting_up: loop {
        println!("count = {}", count);
        let mut remaining = 10;

        loop {
            println!("remaining = {}", remaining);
            if remaining == 9 {
                break;
            }
            if count == 2 {
                break 'counting_up;
            }
            remaining -= 1;
        }

        count += 1;
    }
    println!("End count = {}", count);
}

外側のループには'counting_upというラベルがついていて、0から2まで数え上げます。 内側のラベルのないループは10から9までカウントダウンします。最初のラベルの無いbreakは内側のループを終了させます。 break 'counting_up;は外側のループを終了させます。 このコードは以下のような出力をします。

$ cargo run
   Compiling loops v0.1.0 (file:///projects/loops)
    Finished dev [unoptimized + debuginfo] target(s) in 0.58s
     Running `target/debug/loops`
count = 0
remaining = 10
remaining = 9
count = 1
remaining = 10
remaining = 9
count = 2
remaining = 10
End count = 2

whileで条件付きループ

プログラムにとってループ内で条件式を評価できると、有益なことがしばしばあります。条件が真の間、 ループが走るわけです。条件が真でなくなった時にプログラムはbreakを呼び出し、ループを終了します。 このタイプのループは、loopifelsebreakを組み合わせることでも実装できます; お望みなら、プログラムで今、試してみるのもいいでしょう。

しかし、このパターンは頻出するので、Rustにはそれ用の文法要素が用意されていて、whileループと呼ばれます。 リスト3-3は、whileを使用しています: プログラムは3回ループし、それぞれカウントダウンします。 それから、ループ後に別のメッセージを表示して終了します:

ファイル名: src/main.rs

fn main() {
    let mut number = 3;

    while number != 0 {
        println!("{}!", number);

        number -= 1;
    }

    // 発射!
    println!("LIFTOFF!!!");
}

リスト3-3: 条件が真の間、コードを走らせるwhileループを使用する

この文法要素により、loopifelsebreakを使った時に必要になるネストがなくなり、 より明確になります。条件が真の間、コードは実行されます; そうでなければ、ループを抜けます.

forでコレクションを覗き見る

while要素を使って配列などのコレクションの要素を覗き見ることができます。例えば、リスト3-4を見ましょう。

ファイル名: src/main.rs

fn main() {
    let a = [10, 20, 30, 40, 50];
    let mut index = 0;

    while index < 5 {
        // 値は{}です
        println!("the value is: {}", a[index]);

        index += 1;
    }
}

リスト3-4: whileループでコレクションの各要素を覗き見る

ここで、コードは配列の要素を順番にカウントアップして覗いています。番号0から始まり、 配列の最終番号に到達するまでループします(つまり、index < 5が真でなくなる時です)。 このコードを走らせると、配列内の全要素が出力されます:

$ cargo run
   Compiling loops v0.1.0 (file:///projects/loops)
    Finished dev [unoptimized + debuginfo] target(s) in 0.32s
     Running `target/debug/loops`
the value is: 10
the value is: 20
the value is: 30
the value is: 40
the value is: 50

予想通り、配列の5つの要素が全てターミナルに出力されています。index変数の値はどこかで5という値になるものの、 配列から6番目の値を拾おうとする前にループは実行を終了します。

しかし、このアプローチは間違いが発生しやすいです; 添え字の長さが間違っていれば、 プログラムはパニックしてしまいます。また遅いです。 コンパイラが実行時にループの各回ごとに境界値チェックを行うようなコードを追加するからです。

より効率的な対立案として、forループを使ってコレクションの各アイテムに対してコードを実行することができます。 forループはリスト3-5のこんな見た目です。

ファイル名: src/main.rs

fn main() {
    let a = [10, 20, 30, 40, 50];

    for element in a {
        println!("the value is: {}", element);
    }
}

リスト3-5: forループを使ってコレクションの各要素を覗き見る

このコードを走らせたら、リスト3-4と同じ出力が得られるでしょう。より重要なのは、 コードの安全性を向上させ、配列の終端を超えてアクセスしたり、 終端に届く前にループを終えてアイテムを見逃してしまったりするバグの可能性を完全に排除したことです。

例えば、リスト3-4のコードで、a配列からアイテムを1つ削除したのに、条件式をwhile index < 4にするのを忘れていたら、 コードはパニックします。forループを使っていれば、配列の要素数を変えても、 他のコードをいじることを覚えておく必要はなくなるわけです。

forループのこの安全性と簡潔性により、Rustで使用頻度の最も高いループになっています。 リスト3-3でwhileループを使ったカウントダウンサンプルのように、一定の回数、同じコードを実行したいような状況であっても、 多くのRustaceanは、forループを使うでしょう。どうやってやるかといえば、 Range型を使うのです。Range型は、標準ライブラリで提供される片方の数字から始まって、 もう片方の数字未満の数値を順番に生成する型です。

forループと、まだ話していない別のメソッドrevを使って範囲を逆順にしたカウントダウンはこうなります:

ファイル名: src/main.rs

fn main() {
    for number in (1..4).rev() {
        println!("{}!", number);
    }
    println!("LIFTOFF!!!");
}

こちらのコードの方が少しいいでしょう?

まとめ

やりましたね!結構長い章でした: 変数、スカラー値と複合データ型、関数、コメント、if式、そして、ループについて学びました! この章で議論した概念について経験を積みたいのであれば、以下のことをするプログラムを組んでみてください:

  • 温度を華氏と摂氏で変換する。
  • フィボナッチ数列のn番目を生成する。
  • クリスマスキャロルの定番、"The Twelve Days of Christmas"の歌詞を、 曲の反復性を利用して出力する。

次に進む準備ができたら、他の言語にはあまり存在しないRustの概念について話しましょう: 所有権です。