Rustの制御構文(if・match・loop)を完全解説!条件分岐と繰り返しの基礎
生徒
「Rustを学び始めたんですが、プログラムの流れをどうやって変えるのか知りたいです。他の言語と同じようにifやforを使えばいいんでしょうか?」
先生
「基本は似ていますが、Rustの制御構文には独特な特徴がありますよ。例えば、ifが文ではなく『式』として値を返したり、強力なパターンマッチングを持つmatchがあったりします。」
生徒
「式として値を返す…?なんだか難しそうですが、使いこなせると便利そうですね!」
先生
「その通りです!Rustの制御構文をマスターすれば、より安全で簡潔なコードが書けるようになります。まずは基本のifから、loopやwhile、そしてRustの目玉であるmatchまで順番に見ていきましょう!」
1. Rustの制御構文とは?プログラムの流れを制御する基本概念
プログラミングにおける制御構文とは、ソースコードが上から下へ順番に実行されるだけの流れを変え、特定の条件に応じて処理を分岐させたり、同じ処理を何度も繰り返したりするための仕組みです。Rust言語においても、この制御構文は非常に重要な役割を果たします。
Rustの制御構文が他のプログラミング言語、例えばC言語やJava、Pythonなどと大きく異なる点は、多くの構文が「式(Expression)」として扱われる点です。式であるということは、その処理の結果として値を返すことができるという意味です。これにより、変数への代入と条件分岐を組み合わせるなど、非常にスマートな記述が可能になります。
Rustには主に以下の3種類の制御フローが存在します。一つ目は条件によって道を選ぶ「条件分岐(if、match)」、二つ目は決まった回数や条件を満たすまで繰り返す「ループ(loop、while、for)」、そして三つ目はエラーハンドリングなどでも活躍する「パターンマッチング」です。これらの概念を理解することで、Rust特有のメモリ安全性を活かしつつ、効率的なロジックを構築できるようになります。
2. 条件分岐の基本となるif式の使い方と特徴
Rustのifは、条件が真(true)か偽(false)かに基づいてコードを実行します。ここで最も注意すべき点は、条件式は必ずbool型でなければならないというルールです。他の言語のように、数値の「0」が偽で「1」が真といった曖昧な評価は許されません。コンパイラが厳格にチェックするため、論理的なミスを未然に防ぐことができます。
基本的な構文はif 条件 { 処理 } else { 処理 }という形式ですが、Rustではif全体が値を返すことができるため、三項演算子のような使い方も可能です。以下のサンプルコードで、基本的な条件分岐と、変数への代入にifを使う方法を確認してみましょう。
fn main() {
let number = 7;
// 基本的なif-else文
if number < 5 {
println!("数値は5未満です");
} else {
println!("数値は5以上です");
}
// ifを式として使い、変数に結果を代入する
let condition = true;
let result = if condition { "合格" } else { "不合格" };
println!("結果は{}です", result);
}
数値は5以上です
結果は合格です
上記の例では、result変数に対してifの結果を直接代入しています。このとき、ifブロックとelseブロックが返す値の型は必ず一致していなければなりません。一方が文字列で、もう一方が数値というような書き方をするとコンパイルエラーになります。これは、Rustが静的型付け言語であり、実行前に変数の型を確定させる必要があるためです。
3. 強力なパターンマッチングを実現するmatch構文
Rustの中で最も強力で、頻繁に使われる制御構文の一つがmatchです。他の言語のswitch文に似ていますが、より多機能で安全です。matchは、ある値を複数の「パターン」と比較し、最初に一致したパターンのコードを実行します。最大の特徴は、すべての可能性を網羅(網羅性チェック)していなければコンパイルが通らないという点です。
これにより、例えば列挙型(Enum)などで定義した状態の処理漏れを完全に防ぐことができます。また、数値の範囲指定や、構造体の分解など、高度なパターンマッチングも得意としています。初心者の方は、まずは数値や文字列の振り分けから慣れていくのが良いでしょう。
fn main() {
let card_value = 11;
match card_value {
1 => println!("エースです"),
2..=10 => println!("数字のカードです"),
11 | 12 | 13 => println!("絵札です"),
_ => println!("無効なカードです"),
}
}
絵札です
このコードにある_(アンダースコア)は「ワイルドカード」と呼ばれ、それまでのどのパターンにも一致しなかった場合に実行される処理を記述します。Rustのコンパイラは、すべてのパターンがカバーされているかを厳しくチェックするため、この_を適切に使うことが重要です。漏れがあると「non-exhaustive patterns」というエラーが発生し、プログラムを動かすことすらできません。この厳格さがRustの信頼性を支えています。
4. loopによる無限ループとbreakによる脱出
繰り返し処理の中で、最もシンプルなのがloopです。これは明示的に停止させない限り、永遠にそのブロック内の処理を繰り返します。一見すると危険な構文に思えるかもしれませんが、サーバーの待機処理や、特定の条件を満たすまでリトライし続けたい場合に非常に便利です。
ループを抜けるにはbreakキーワードを使用します。また、特定の回をスキップして次の繰り返しに進むにはcontinueを使用します。Rustのloopは他の言語の無限ループと異なり、ループ内で計算した結果をbreakを使って外に持ち出すことができるという面白い特徴があります。
fn main() {
let mut counter = 0;
let result = loop {
counter += 1;
if counter == 10 {
break counter * 2; // ループを抜ける際に値を返す
}
};
println!("ループの結果は: {}", result);
}
ループの結果は: 20
この例では、カウンターが10になった瞬間にbreakが実行され、同時にcounter * 2という値を返しています。その値が変数resultに代入されます。無限ループから値を生成するという書き方は、Rustならではの洗練された手法と言えるでしょう。このように、ただ繰り返すだけでなく、処理の結果を安全に次の工程へ渡すことが可能です。
5. 条件付きループwhileとコレクションに最適なfor
条件が真である間だけ繰り返したい場合はwhileを使用します。これは他の言語とほぼ同じ挙動をします。しかし、Rustにおいて配列やベクタ(Vector)などのコレクションの中身を順番に処理したい場合は、forを使うのが一般的であり、最も推奨される方法です。
なぜforが推奨されるのかというと、配列の範囲外アクセスを防ぐことができ、かつ実行時のオーバーヘッドが少ないからです。whileでインデックスを使って配列にアクセスする場合、毎回「範囲内かどうか」のチェックが入りますが、forとイテレータを組み合わせれば、Rustはより最適化された安全な方法でデータを処理してくれます。
fn main() {
let fruits = ["りんご", "バナナ", "オレンジ"];
// for文を使って配列の要素を一つずつ取り出す
for fruit in fruits.iter() {
println!("私は{}が好きです", fruit);
}
// 数値の範囲を指定した繰り返し(1から4まで)
for number in 1..5 {
println!("カウント: {}", number);
}
}
私はりんごが好きです
私はバナナが好きです
私はオレンジが好きです
カウント: 1
カウント: 2
カウント: 3
カウント: 4
1..5という書き方は「レンジ(Range)」と呼ばれ、1以上5未満の範囲を表します。もし5まで含めたい場合は1..=5と記述します。このように直感的に範囲を指定できるのもRustの魅力です。コレクションの反復処理においてforは欠かせない存在であり、プログラムの可読性を大きく向上させてくれます。
6. 複雑な条件を整理するif letとwhile letの活用
最後に、少し発展的な内容ですが、初心者の方も知っておくと便利な「糖衣構文(シンタックスシュガー)」を紹介します。それがif letです。これは、特定のパターンに一致する場合だけ処理を行い、それ以外は何もしない(あるいはelseで受ける)という状況で、matchをより短く書くために用意されています。
例えば、Option型(値があるか空かを表す型)から値を取り出す際、matchを使うと「値がある場合」と「ない場合」の両方を書かなければなりませんが、if letなら値がある場合だけをスマートに記述できます。同様にwhile letを使うと、パターンに一致し続ける間だけループを回すといった制御が可能になります。
Rustの制御構文は、単なる条件分岐や繰り返しを超えて、プログラムの「安全性」と「表現力」を両立させるために設計されています。if、match、loop、while、forを適切に使い分けることで、バグが少なくメンテナンス性の高いコードが書けるようになります。最初は難しく感じるかもしれませんが、コンパイラのメッセージを読みながら、一つずつ試していくことが上達への近道です。メモリ安全性を保証しながらも、自由自在にプログラムの流れを操る楽しさを、ぜひRustで体感してください。