制御フロー
If
Rust の if 文は、C++ のものとほとんど同じです。違いの 1 つは、
波括弧が必須である一方、テストされる式を囲む括弧は不要であることです。
もう 1 つは、if が式であるため、C++ の三項 ?: 演算子と同じように
使えることです(前のセクションで見たように、ブロック内の最後の式が
セミコロンで終端されていない場合、それがブロックの値になります)。
Rust には三項 ?: はありません。したがって、次の 2 つの関数は同じことをします。
#![allow(unused)] fn main() { fn foo(x: i32) -> &'static str { let result: &'static str; if x < 10 { result = "less than 10"; } else { result = "10 or more"; } return result; } fn bar(x: i32) -> &'static str { if x < 10 { "less than 10" } else { "10 or more" } } }
(なぜ mut result ではないのでしょうか? foo のコードでは result は不変であり、
単に 2 つの可能な場所で初期化されているだけです。Rust は return result の時点までに、
それが初期化済みであることが保証されていると判断できます。)
1 つ目は、C++ で書くであろうものをかなり直訳したものです。 2 つ目のほうが、より Rust らしいスタイルです。
let result = if x < 10 ... などのように書くこともできます。
ループ
Rust には while ループがあり、これも C++ と同じです。
fn main() { let mut x = 10; while x > 0 { println!("Current value: {}", x); x -= 1; } }
Rust には do...while ループはありませんが、永遠にループするだけの
loop 文があります。
fn main() { loop { println!("Just looping"); } }
Rust には C++ と同じように break と continue があります。
for ループ
Rust にも for ループがありますが、これは少し異なります。整数のベクターがあり、
それらをすべて出力したいとしましょう(ベクター/配列、イテレーター、ジェネリクスについては、
今後より詳しく扱います。現時点では、Vec<T> は T の列であり、iter() は、
反復したいと思うようなものからイテレーターを返す、ということを知っていれば十分です)。
単純な for ループは次のようになります。
#![allow(unused)] fn main() { fn print_all(all: Vec<i32>) { for a in all.iter() { println!("{}", a); } } }
TODO all.iter() の代わりに &all/all も
all のインデックスを使って走査したい場合(標準的な C++ の配列に対する
for ループに少し近い形)、次のようにできます。
#![allow(unused)] fn main() { fn print_all(all: Vec<i32>) { for i in 0..all.len() { println!("{}: {}", i, all[i]); } } }
len 関数が何をするかは、うまくいけば明らかでしょう。TODO 範囲記法
前の例に相当する、より Rust らしい書き方は、列挙イテレーターを使うことです。
#![allow(unused)] fn main() { fn print_all(all: Vec<i32>) { for (i, a) in all.iter().enumerate() { println!("{}: {}", i, a); } } }
ここで enumerate() はイテレーター iter() からチェーンされ、反復中に現在の
カウントと要素を生成します。
次の例には、Borrowed Pointers のセクションで扱うより高度なトピックが含まれています。
整数のベクターがあり、そのベクターを参照渡しで関数に渡して、その場で変更したいとしましょう。
ここで for ループは可変参照を与える可変イテレーターを使用します。
* によるデリファレンスは C++ プログラマーにはおなじみのはずです。
#![allow(unused)] fn main() { fn double_all(all: &mut Vec<i32>) { for a in all.iter_mut() { *a += *a; } } }
Switch/Match
Rust には、C++ の switch 文に似ているものの、はるかに強力な match 式があります。 この単純なバージョンはかなり見慣れたものに見えるはずです。
#![allow(unused)] fn main() { fn print_some(x: i32) { match x { 0 => println!("x is zero"), 1 => println!("x is one"), 10 => println!("x is ten"), y => println!("x is something else {}", y), } } }
構文上の違いがいくつかあります。一致した値から実行する式へ進むために => を使い、
match のアームは , で区切られます(最後の , は省略可能です)。
それほど明白ではない意味論上の違いもあります。一致させるパターンは網羅的でなければなりません。
つまり、一致対象の式(上の例では x)の取り得るすべての値がカバーされていなければなりません。
y => ... の行を削除して、何が起こるか試してみてください。これは、0、1、10 に対する
マッチしかなく、明らかにそれ以外にもマッチされない整数がたくさんあるためです。
最後のアームでは、y が一致対象の値(この場合は x)に束縛されます。
次のように書くこともできます。
#![allow(unused)] fn main() { fn print_some(x: i32) { match x { x => println!("x is something else {}", x) } } }
ここでは、match アーム内の x が新しい変数を導入し、引数の x を隠蔽します。
これは内側のスコープで変数を宣言するのと同じです。
変数に名前を付けたくない場合は、名前のない変数として _ を使うことができます。
これはワイルドカードマッチのようなものです。何もしたくない場合は、空の分岐を用意できます。
#![allow(unused)] fn main() { fn print_some(x: i32) { match x { 0 => println!("x is zero"), 1 => println!("x is one"), 10 => println!("x is ten"), _ => {} } } }
もう 1 つの意味論上の違いは、あるアームから次のアームへのフォールスルーがないことです。
そのため、if...else if...else のように動作します。
後の投稿で、match が非常に強力であることを見ていきます。今は、さらに 2 つだけ機能を紹介したいと思います。
値に対する「or」演算子と、アーム上の if 節です。例を見れば自明であることを期待します。
#![allow(unused)] fn main() { fn print_some_more(x: i32) { match x { 0 | 1 | 10 => println!("x is one of zero, one, or ten"), y if y < 20 => println!("x is less than 20, but not zero, one, or ten"), y if y == 200 => println!("x is 200 (but this is not very stylish)"), _ => {} } } }
if 式と同じように、match 文も実際には式であるため、
最後の例を次のように書き換えることができます。
#![allow(unused)] fn main() { fn print_some_more(x: i32) { let msg = match x { 0 | 1 | 10 => "one of zero, one, or ten", y if y < 20 => "less than 20, but not zero, one, or ten", y if y == 200 => "200 (but this is not very stylish)", _ => "something else" }; println!("x is {}", msg); } }
閉じ波括弧の後のセミコロンに注意してください。これは let 文が文であり、
let msg = ...; という形式を取らなければならないためです。右辺には match 式を入れています
(通常はセミコロンを必要としません)が、let 文にはセミコロンが必要です。
私はこれによく引っかかります。
動機: Rust の match 文は、C++ の switch 文でよくあるバグを避けます。
break を忘れて意図せずフォールスルーすることはありません。また、enum にケースを追加した場合
(詳しくは後で扱います)、コンパイラーがそれが match 文でカバーされていることを確認してくれます。
メソッド呼び出し
最後に、Rust にも C++ と同様にメソッドが存在することを簡単に述べておきます。
メソッドは常に . 演算子を介して呼び出されます(-> はありません。これについては別の投稿でさらに説明します)。
上でいくつか例を見ました(len、iter)。それらがどのように定義され、呼び出されるかについては、
今後さらに詳しく見ていきます。C++ や Java から推測するであろうほとんどの仮定は、おそらく正しいです。