制御フロー
条件が真かどうかによってコードを走らせるかどうかを決定したり、
条件が真の間繰り返しコードを走らせるか決定したりすることは、多くのプログラミング言語において、基本的な構成ブロックです。
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
で複数の条件を扱う
if
とelse
を組み合わせて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); }
この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);
}
このコードをコンパイルしようとすると、エラーになります。if
とelse
アームは互換性のない値の型になり、
コンパイラがプログラム内で問題の見つかった箇所をズバリ指摘してくれます:
$ 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種類のループが存在します: loop
とwhile
とfor
です。それぞれ試してみましょう。
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
はループの中で残っているコードをスキップして次のループに移るためのものです。
ループ内にループがある場合、break
とcontinue
は最も内側のループに適用されます。
ループラベルを使用することで、break
やcontinue
が適用されるループを指定することができます。
以下に例を示します。
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
を呼び出し、ループを終了します。
このタイプのループは、loop
、if
、else
、break
を組み合わせることでも実装できます;
お望みなら、プログラムで今、試してみるのもいいでしょう。
しかし、このパターンは頻出するので、Rustにはそれ用の文法要素が用意されていて、while
ループと呼ばれます。
リスト3-3は、while
を使用しています: プログラムは3回ループし、それぞれカウントダウンします。
それから、ループ後に別のメッセージを表示して終了します:
ファイル名: src/main.rs
fn main() { let mut number = 3; while number != 0 { println!("{}!", number); number -= 1; } // 発射! println!("LIFTOFF!!!"); }
この文法要素により、loop
、if
、else
、break
を使った時に必要になるネストがなくなり、
より明確になります。条件が真の間、コードは実行されます; そうでなければ、ループを抜けます.
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; } }
ここで、コードは配列の要素を順番にカウントアップして覗いています。番号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-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の概念について話しましょう: 所有権です。