【Rust】ループとイテレータの使い方

2020-06-13

Rustでよくわからずに for ループを使っていたらますますわからないことが増えてきたので、ちゃんとした使い方を調べた。

単純な使用例

ループをさせたい場合 for-in が利用できる:

fn main() {
// 所定回数ループしたい場合
for i in 0..3 {
println!("{}", i);
}
//=> 0, 1, 2

// ベクトルの各要素になにか処理したい場合
let v = vec![11, 22, 33];
for x in v {
println!("{}", x);
}
//=> 11, 22, 33
}

この書式は他の動的スクリプティング言語とパッと見たところ同じ:

for i in range(3):
print(i)

for x in [11, 22, 33]:
print(x)

なので、同じように使えるのかと思ってしまう。 しかし実際にはだいぶ違うので、同じアナロジーで理解しているとハマることになる。

ハマリポイント1:配列で使えない

ベクトルが使えるんだから配列も同じように使えるだろうと推測してしまうんだけど、できない:

fn main() {
let array = [1, 2, 3];
for x in array {
println!("{}", x);
}
}
error[E0277]: `[{integer}; 3]` is not an iterator
--> src/main.rs:3:14
|
3 | for x in array {
| ^^^^^ borrow the array with `&` or call `.iter()` on it to iterate over it
|
= help: the trait `std::iter::Iterator` is not implemented for `[{integer}; 3]`
= note: arrays are not iterators, but slices like the following are: `&[1, 2, 3]`
= note: required by `std::iter::IntoIterator::into_iter`

エラーメッセージの通り、 &array とスライスにするか、 .iter() としてやる必要がある。

ハマリポイント2:スライスやiter()の値を渡そうとするとエラーが出る

上記の続きでスライスや .iter() を使ってもループ変数 xprintln! で普通に表示されているので要素型なのかと思ってしまうが、違う:

fn for_array2() {
let array = [1, 2, 3];
for x in array.iter() {
let y = square(x); // <= エラー
println!("{}", y);
}
}

fn square(x: i32) -> i32 {
x * x
}
error[E0308]: mismatched types
--> src/main.rs:4:24
|
4 | let y = square(x);
| ^
| |
| expected `i32`, found `&{integer}`
| help: consider dereferencing the borrow: `*x`

エラーメッセージの通り、 *x としてやる必要がある。

ハマリポイント3:ベクトルが移動されてしまう

ベクトルを for に与えると移動されてしまって、その後は使用できない:

fn main() {
let v = vec![1, 2, 3];
for x in v {
println!("{}", x);
}
println!("{:?}", v); // <= vが移動済みなので、使用できない
}
error[E0382]: borrow of moved value: `v`
--> src/main.rs:4:22
|
2 | let v = vec![1, 2, 3];
| - move occurs because `v` has type `std::vec::Vec<i32>`, which does not implement the `Copy` trait
3 | for _x in v {}
| -
| |
| value moved here
| help: consider borrowing to avoid moving into the for loop: `&v`
4 | println!("{:?}", v);
| ^ value borrowed here after move

続けて使用できるようにするにはスライスや参照を渡せばよい。

forループはなんなのか?

for-in にベクトルは与えられるけど配列は与えられないというのはどういうことなのか、 どういう場合に使えるのか?

ドキュメントのfor Loops and IntoIterator を読むと、 for ループはシンタックスシュガーで IntoIteratorトレイトを実装している型に対して使える、とのこと。 (なぜベクトルは IntoIterator トレイトが実装されているのに、配列にないのかというのが謎だが…)

同じページ内の上部、反復の3形式によると、

  • iter(), &T で反復
  • iter_mut(), &mut T で反復
  • into_iter(), T で反復

ということを把握しておくと理解しやすいように思う。

forループ、イテレータ使用のイディオム

内容更新

.iter_mut() を使うとループ変数に &mut T が入ってくるので、更新できる:

let mut array = [1, 2, 3];
for slot in array.iter_mut() {
*slot += *slot;
}
println!("{:?}", array);
// [2, 4, 6]

イテレーション中にも更新できるというのがポイント。

イテレータで使えるメソッド

Iteratorトレイトにはいろいろメソッドが用意されているので、メソッドをつなげていわゆる mapfilter などいろいろなメソッドを使える。 その中でもよく使いそうなものの一部を列挙:

  • map: 値の変換
  • filter: 条件に合う要素を取り出す
  • fold: 畳み込み
  • count: 個数
  • enumerate: イテレーションが何番目かというのを加える
  • position: 条件に合う要素のインデクスを返す
  • all: 全要素が条件を満たすか?
  • any: 1つでも条件を満たすか?
  • collect: イテレータをコレクションに変換

他、多数…。

結論

  • 基本的には移動はさせたくない場合が多いと思うので、.iter() の参照イテレータを使用する
  • 更新する場合には .iter_mut()
  • .into_iter() だと移動が発生する

参考