さて、Rustの制御構造も見ていきましょう。制御構造は一般的に逐次実行、条件分岐、繰り返しの3つがあります。逐次実行は第2回でやってきたような上から下に順番に実行する構造です。逐次実行は省略して条件分岐を取り上げます。なお、今回のサンプルコードは、全てcontrolsパッケージに作成していきます。
Rustでは、条件分岐はif式を利用します。「式」とあるように、条件分岐の実行結果全体が最終的に評価されて値を持ちます。if式の返す値が不要であれば、それは一般的なif文と同様になります。
let age = 15; // 年齢 if age >= 25 { // 条件式はfalseとなる println!("選挙権と被選挙権があります。"); // 実行されない } else if age >= 18 { // 条件式はfalseとなる println!("選挙権のみがあります。"); // 実行されない } else { println!("選挙権も被選挙権もありません。"); // 実行される }
ifキーワードに続く「age >= 25」は条件式で小かっこは不要です。条件式の評価結果が論理値のtrueなら、続くブロックの中身が実行されます。falseになれば、else式に続くブロックの中身が実行されます。上記のようにelse式でなくelse if式になっている場合は、さらに条件式「age >= 18」が評価され、同様にどちらかのブロックの中身が実行されます。
if式には中かっこ({〜})で囲まれたブロックが必要です。ブロックは、すでにmain()関数のブロックとして登場してきていましたが、複数の文をまとめたものです。文が1個でもブロックは省略できません。
if式が値を返す場合を見てみましょう。値を返す場合はそれぞれのブロックの最後にif式の値とする式を置きます。
let age = 15; // 年齢 let s = if age >= 25 { // sをif式の結果で初期化。条件式はfalseとなる "選挙権と被選挙権があります。" // if式の値にならない } else if age >= 18 { // 条件式はfalseとなる "選挙権のみがあります。" // if式の値にならない } else { "選挙権も被選挙権もありません。" // if式の値になる }; // セミコロンが必要 println!("{}", s); // 「選挙権も被選挙権もありません。」
値を返すif式にする場合は、以下の点に気を付ける必要があります。
Rustでは条件式は必ず論理値を返すbool型の結果にする必要があります。C/C++では0か0以外かという判定をしていましたが、これには利点もある反面、さまざまなバグの原因となるものでした。Rustはそこが厳格化され、必ず論理値にならなくてはならないというようになっています。従って、以下のようなコードはコンパイルしてもエラーが出力されます。
let age = 20; if age { // 論理型に評価できないのでエラーになる println!("0歳児ではありません。"); }
error[E0308]: mismatched types --> src/bin/if3.rs:4:8 | 4 | if age { | ^^^ expected `bool`, found integer // boolなのにintegerがあった
条件分岐には、match式というものもあります。match式は、C/C++などのswitch文に似た動きをする構文で、与えられた変数に対してパターンマッチングをして、合致すれば指定された文を実行します。下記のコードはchar型の変数の値でメッセージの表示を切り替える例です。match式も値を返すことができるので、それを含めた例にしています。
let letter = 'S'; let str = match letter { // letterでマッチングする 'Z' => "アルファベット最後の文字", // 単一値のマッチング 'S' | 'M' | 'L' => "サイズを表すアルファベット", // 複数値のマッチング '0'..='9' | 'A'..='F' => "16進数で使う文字", // 範囲を指定したマッチング _ => "いずれでもない文字", // いずれにもマッチしなかった場合 }; println!("{}は{}です。", letter, str);
match式におけるパターンマッチングの主要な方法である単一値、複数値、値の範囲、そしていずれにもマッチしなかった場合を紹介しました。match式では、パターンは全てのケースを網羅するようにしなければなりません。アンダースコア(_)によるパターンがあれば取りあえず満たせますが、そうでない場合はくまなくパターンを記述する必要があります。
また「パターン => 実行する文および式」はカンマ(,)で区切り、文および式が複数になる場合には中かっこ({ })で囲ってブロックにします。さらに、パターンは定数である必要がなく、式も指定できます。この場合でも、全てのケースを網羅するようにしなければなりません。
条件分岐に続く制御構造は繰り返し(ループ)です。ループには3種類の構文が用意されています。while式、for式、それにloop式です。if式と同様に、繰り返しの構文も条件付きですが値を返します。順番に見てみましょう。
while式もif式と同様に、条件式がtrueに評価されればブロック内を実行します。こちらも条件式に小かっこは不要ですが、ブロックは必要です。while式を使って、1から変数maxまでの数の総計を求めて表示してみましょう。
let max = 10; let mut count = 1; let mut sum = 0; while count <= max { sum += count; count += 1; } println!("{}までの合計は{}です。", max, sum); // 「10までの合計は55です。」
if式とmatch式は条件分岐の結果としての値を返すことができましたが、while式では値を返すことはできません。正確には、空のタプルである()が返されます。
Rustのfor式は、配列などのコレクションに対して利用します。C/C++などで伝統的に使われている初期化、条件、繰り返し前処理のような構文はありません。このような構文は、while式で記述します。for文を使って配列scoresの要素を1つずつ取り出し、表示してみます。
let scores = [90, 80, 85, 70, 95]; // 点数の配列 for score in scores.iter() { // for式で回す println!("点数は{}点です。", score); // 配列の要素数だけ実行される }
% cargo run --bin for1 点数は90点です。 点数は80点です。 点数は85点です。 点数は70点です。 点数は95点です。
コレクションのiter()メソッドを呼び出すことに注意してください。これはイテレータ(繰り返し記述子)で、コレクションの中身を順番に変数に与えるために使用します。配列の内容を順番に表示するだけなら、while式も利用できます。しかし、while式だと配列の各要素に対するインデックスを正確に指定してあげなければならず、しばしばバグの原因となります。for式も、while式同様に値を返すことはできません。正確には、空のタプルである()が返されます。
繰り返しの最後はloop式です。これは最もシンプルな繰り返しの構文です。
loop { // 永遠に続くのでCtrl+Cを入力して止める println!("線路は続くよ、どこまでも。"); }
見て分かるように条件も何もなく、ひたすら繰り返すだけです。止め方は後で考えるので取りあえず実行してほしいというケースで使います(終了条件が明確なのにloop式を安易に利用するのは非推奨です)。
無限ループなので、どこかで止めないとプログラムは実行し続けます。[Ctrl]+[C]キーか、あるいは次で取り上げるbreak式を使って終了させることができます。loop式は値を返すことができ、値を返すにもbreak式を使います。
繰り返しを強制的に止める(繰り返しから抜ける)break式を使うと、繰り返しをその時点で止められる他、loop式として値を返すことができます。配列scoresから値を探し、見つかればインデックスを繰り返しの値として返してみましょう。なお、このソースコードには欠陥があり、もし値が見つからない場合はインデックスが範囲外になりpanicとなります。scoresの値を書き換えて試してみてください。
let scores = [90, 20, 100, 40, 60]; let mut i = 0; let f = loop { if scores[i] == 100 { // 配列に100が見つかれば中断してインデックスを返す break i; } i += 1; }; println!("満点が要素{}にありました。", f); // 「満点が要素2にありました。」
break式の値は省略できます。省略した場合、単純にループを中断するだけになりますが、loop式が値を返すことはできなくなります。なお、while式もfor式もbreak式を利用できますが、loop式のように値を返すことはできません。中断のみの用途になります。
continue式は、ブロックの途中から次の繰り返しに強制的に移動させます。continue式を使ってみましょう。
let scores = [90, 20, 100, 40, 60]; let mut i = 5; while i > 0 { i -= 1; if scores[i] == 100 { // 配列に100が見つかれば次の繰り返しに移す continue; } println!("満点でない点数{}が要素{}にありました。", scores[i], i); }
% cargo run --bin continue1 満点でない点数60が要素4にありました。 満点でない点数40が要素3にありました。 満点でない点数20が要素1にありました。 満点でない点数90が要素0にありました。
配列scoresの要素が100であれば、その値だけ表示しないプログラムです。なお、continue式は値を返すためには使えません。また値を指定するとコンパイルエラーになります。
今回は、Rustのリテラルと制御構文(条件分岐、繰り返し)を紹介しました。次回は、基本的な文法の最後として、関数を紹介します。
WINGSプロジェクト
有限会社 WINGSプロジェクトが運営する、テクニカル執筆コミュニティー(代表山田祥寛)。主にWeb開発分野の書籍/記事執筆、翻訳、講演等を幅広く手掛ける。2021年10月時点での登録メンバーは55人で、現在も執筆メンバーを募集中。興味のある方は、どしどし応募頂きたい。著書、記事多数。
・サーバーサイド技術の学び舎 - WINGS(https://wings.msn.to/)
・RSS(https://wings.msn.to/contents/rss.php)
・Twitter: @yyamada(https://twitter.com/yyamada)
・Facebook(https://www.facebook.com/WINGSProject)
Copyright © ITmedia, Inc. All Rights Reserved.