iter()とinto_iter()の違いを整理した

VectorIteratorに変換する時にいつも混乱していたので整理した。

混乱

あるVectorの要素すべてを3倍するコードを考える。

fn main() {
    let vec1 = vec![1,2,3,4,5];
    let vec2 = vec1.iter()
                   .map(|i| i * 3)
                   .collect::<Vec<i32>>();

    println!("{:?}", vec1);
    println!("{:?}", vec2);
}

このコードはコンパイルできるが、以下のような疑問がある。

  1. 5行目で vec1はなぜ使えるのか? 3行目の vec1.iter()で使われているじゃないか!
  2. map(|i| i * 3)iは参照なのか値なのか?

これらの疑問に関する答えは、

  1. iter()Vectorをmoveしない。into_iter()Vectorをmoveする。
  2. iter()Vectorから「参照のコレクションであるIterator」を作成し、into_iter()Vectorから「値のコレクションであるIterator」を作成する。

ということになる。

into_iter()の挙動

呼び出し元のVectorの所有権

fn main() {
    let vec1 = vec![1,2,3,4,5];
    let vec2 = vec1.into_iter()
                   .map(|i| i * 3)
                   .collect::<Vec<i32>>();

    println!("{:?}", vec1); // Error!
}

このコードはコンパイルできない。というのも、3行目の into_iter()vec1をmoveするからである。

このように、 into_iter()は、呼び出し元のVectorをmoveする。

mapに渡される要素は参照なのか?

fn main() {
    let vec1 = vec![1,2,3,4,5];
    let vec2 = vec1.into_iter()
                   .map(|i| *i * 3) // Error!
                   .collect::<Vec<i32>>();
}

このコードはコンパイルできない。というのも、4行目の map(|i| *i * 3)iは、参照ではなく値だからである。

このように、into_iter()に連なる mapには、参照ではなくが渡される。

iter()の挙動

呼び出し元のVectorの所有権

fn main() {
    let vec1 = vec![1,2,3,4,5];
    let vec2 = vec1.iter()
                   .map(|i| i * 3)
                   .collect::<Vec<i32>>();

    println!("{:?}", vec1); // OK!
}

このコードはコンパイルできる。iter()vec1をmoveしないからである。

このように、 iter()は、呼び出し元のVectorをmoveしない。

mapに渡される要素は参照なのか?

fn main() {
    let vec1 = vec![1,2,3,4,5];
    let vec2 = vec1.iter()
                   .map(|i| *i * 3) // OK!
                   .collect::<Vec<i32>>();
}

このコードはコンパイルできる。4行目の map(|i| *i * 3)iは、値ではなく参照だからである。

このように、iter()に連なる mapには、参照が渡される。

結論

VectorIteratorに変換して処理を行いたいとき、

  • そのVectorを後で利用するならiter()を使う。その場合、mapには要素の参照が渡される。
  • そのVectorを後で利用しないならinto_iter()を使う。その場合、mapには要素の値が渡される。

速度について

一応簡単にベンチマークして、iter()into_iter()の間に速度的な差があるのか検証した。 なんとなくiter()の方が遅いような気がするが、誤差幅を考えるとほとんど差がないように見える。

ベンチマークのコード:

#![feature(test)]

extern crate test;

pub fn iter(vec: Vec<i64>) {
    vec.iter().map(|i| i * 2).sum::<i64>();
}

pub fn into_iter(vec: Vec<i64>) {
    vec.into_iter().map(|i| i * 2).sum::<i64>();
}

#[cfg(test)]
mod tests {
    use super::*;
    use test::Bencher;

    #[bench]
    fn bench_iter(b: &mut Bencher) {
        let range = 0..100000;
        let vec = range.collect::<Vec<i64>>();

        b.iter(|| iter(vec.clone()));
    }

    #[bench]
    fn bench_into_iter(b: &mut Bencher) {
        let range = 0..100000;
        let vec = range.collect::<Vec<i64>>();

        b.iter(|| into_iter(vec.clone()));
    }
}

結果:

> cargo bench
   Compiling iterator-bench v0.1.0
    Finished release [optimized] target(s) in 0.61 secs
     Running target/release/deps/iterator_bench-b01db65774dc9996

running 2 tests
test tests::bench_into_iter ... bench:      38,553 ns/iter (+/- 14,812)
test tests::bench_iter      ... bench:      39,507 ns/iter (+/- 20,207)

test result: ok. 0 passed; 0 failed; 0 ignored; 2 measured; 0 filtered out