Rustでベクタをforで回しているコードを見ると、iter()を使っているパターンと使っていないパターンの両方を目にする。
fn main() {
let v = vec![1, 3, 5];
for i in v.iter() {
println!("{:?} ", i);
}
}
fn main() {
let v = vec![1, 3, 5];
for i in &v {
println!("{:?} ", i);
}
}
これらは両方とも同じように動作する。
何か違うの?
結論から言うと、どっちの書き方でも結果は変わらない。
forって何してるの?
よくあるforの内部動作の理解としては、inに指定されているものにnext()を適用して、Noneになるまでループを継続する、というものだと思う。なので、for式のinに指定できるものはイテレータだと思っていた。
イテレータとは何か?それは、Iteratorトレイトを実装している型である。
しかし、ベクタのドキュメントを見てみると、ベクタにIteratorは実装されていないことが分かる。その証拠に、ベクタでnext()を使おうとするとコンパイルエラーになる。
n main() {
let v = vec![1, 3, 5];
println!("{:?} ", v.next()); // Error!
}
ではなぜforで直接ベクタをループできるのか?
今度はforのドキュメントを見てみよう。ドキュメントによると、inに指定されているものでnext()を直接呼ぶのではなく、実はIntoIteratorトレイトで実装されているinto_iter()により変換してから、next()を呼んでいることが分かる。inに指定できるのは、IteratorではなくIntoIteratorを実装している型、つまりイテレータそのものではなくイテレータに変換可能な型なのである。
ベクタにもIntoIteratorが実装されているため、そのものがイテレータでなくともforでループすることができるというカラクリだ。
よって、forでv.iter()と&vを使った時の違いは、イテレータへの変換をプログラマが明示的に行うか、forが内部的に行うかの違いであり、結果は変わらないということが分かった。めでたしめでたし。
イテレータと所有権
v.iter()と&vが変わらないということは分かったが、単純にvをそのままinに指定した場合はどうなるだろうか?
fn main() {
let v = vec![1, 3, 5];
for i in v {
println!("{:?} ", i);
}
}
これも冒頭の2つのコードと全く同じ出力結果になるわけだが、内部的には異なっている。それは、所有権の問題だ。
もう一度vを使おうとするとコンパイルエラーになるので分かる。
fn main() {
let v = vec![1, 3, 5];
for i in v {
println!("{:?} ", i);
}
for i in v { // Error! (value used here after move)
println!("{:?} ", i);
}
}
1回目のループでvの所有権が移動してしまっているため、2回目のループでは使うことができない。回避するには、値を参照すればいいので、1回目のループか両方のvを&vに変更することで2回目のループも問題なくコンパイルできる。
今回のサンプルでは、println!()が自動的に参照外しをするので出力として違いが分かりにくいが、ループ内の処理が少し複雑になる場合は意識する必要があるだろう。
ここまでの流れで分かるように、v.iter()を使うとイテレータは参照として使われる。では所有して使うようにイテレータを生成するにはどうするか?そういった場合はinto_iter()を使えばいい。つまり、forにvとv.into_iter()を使った時の結果は同じになる。
fn main() {
let v = vec![1, 3, 5];
for i in v {
println!("{:?} ", i);
}
}
fn main() {
let v = vec![1, 3, 5];
for i in v.into_iter() {
println!("{:?} ", i);
}
}
所有権の話とは関係ないが、他にはイテレータをミュータブルに使うためのiter_mut()とかもある。
まとめ
ベクタをforで回すときには、ベクタをそのまま渡しても、iter()とかでイテレータ化して渡しても、結果は変わらないことがわかった。
可読性とかを考慮して好みで使えばいいんじゃないでしょうか。