Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
Help us understand the problem. What is going on with this article?

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

More than 1 year has passed since last update.

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

0yoyoyo
Rustでコンパイラを書いてみたいと思ってるマン。
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away