5
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Rustのfor式と、ベクタとイテレータ

Posted at

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()とかでイテレータ化して渡しても、結果は変わらないことがわかった。
可読性とかを考慮して好みで使えばいいんじゃないでしょうか。

5
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?