rust

iter()とinto_iter()の違いとちょっとした落とし穴

説明したのでメモ。

iter()into_iter()の違い

色々なiter()into_iter()について。公式リファレンスを見ればそれぞれの役割はわかります。

There are three common methods which can create iterators from a collection:

iter(), which iterates over &T.
iter_mut(), which iterates over &mut T.
into_iter(), which iterates over T.

Various things in the standard library may implement one or more of the three, where appropriate.

少なくともstdライブラリではiter()iter_mut()で得られるIteratorは&Tを、into_iter()ではTを持っている感じでやりましょう、という慣習があるらしいです(昔の議論とか見てると元々はそうではなかったっぽいですが)。

以下にVectorを使った例を示します。(Rust by Exampleより)

let vec1 = vec![1, 2, 3];
let vec2 = vec![4, 5, 6];
println!("2 in vec1: {}", vec1.iter()     .any(|&x| x == 2));
println!("2 in vec2: {}", vec2.into_iter().any(| x| x == 2));

iter()では参照が、into_iter()では値が渡されているのがわかりますね。

into_iter()forループの関係

また、この中でinto_iter()forループで使われます。これもリファレンスから引用しますが、

let values = vec![1, 2, 3, 4, 5];

for x in values {
    println!("{}", x);
}

これが、

let values = vec![1, 2, 3, 4, 5];
{
    let result = match IntoIterator::into_iter(values) {
        mut iter => loop {
            let next;
            match iter.next() {
                Some(val) => next = val,
                None => break,
            };
            let x = next;
            let () = { println!("{}", x); };
        },
    };
    result
}

これと同値らしいです。なので、for x in valuesするとvaluesはmoveされてしまいます。

for x in values {
    println!("{}", x);
}
let y = values; // move error

初心者がよくつまづくところですね。これを回避するには、

for x in &values {
    println!("{}", x);
}
let y = values; // valid

こうやって、valuesの参照をとってからfor文に入れるんでしたね。何故こうするかは、into_iter()IntoIteratorトレイトで定義されていることがわかれば理解できます。Vectorがどうやってimplしているか見てみます。

impl<T> IntoIterator for Vec<T>
impl<'a, T> IntoIterator for &'a Vec<T>
impl<'a, T> IntoIterator for &'a mut Vec<T>

IntoIterator&Vecにもimplされています。こっちのinto_iter()が呼ばれてたんですね。先ほどリファレンスの引用で、「into_iter()Tを返す」と書きましたが、このTが何かの参照ではない保証はどこにもないので理解が適当だと注意する必要があります。
ちなみに、IntoIteratorはIteratorにもimplされているので、こういうことも可能です。

for x in values.iter() {
    println!("{}", x);
}

into_iter()の変な挙動

ここで、ちょっとした落とし穴について。配列に対してinto_iter()を呼び出してみます。

let array = [1, 2, 3];
println!("2 in array: {}", array.into_iter().any(|x| x == 2));

コンパイルエラーになりました。

error[E0277]: the trait bound `&{integer}: std::cmp::PartialEq<{integer}>` is not satisfied
  --> examples/1.rs:10:61
   |
10 |     println!("2 in array2: {}", array.into_iter().any(|x| x == 2));
   |                                                             ^^ can't compare `&{integer}` with `{integer}`
   |
   = help: the trait `std::cmp::PartialEq<{integer}>` is not implemented for `&{integer}`

お?
参照が返ってきてます。まさか・・?

let array = [1, 2, 3];
println!("2 in array: {}", array.into_iter().any(|&x| x == 2));

|&x|にしたら通りました。into_iter()を読んだのに&Tが返ってきています。これはどういうことでしょうか。
Arrayのリファレンスを見てみましょう。

Arrays of sizes from 0 to 32 (inclusive) implement the following traits if the element type allows it:

  • ...
  • IntoIterator (implemented for &[T; N] and &mut [T; N])
  • ...

[T; N]そのものはIntoIteratorをimplしてしませんが、&[T; N]はしていると書いてありますね。これでわかりました。array.into_iter()の例ではinto_iter()が定義されていないため、自動で&array.into_iter()を呼んでくれていたんですね!わかりづらい

ちなみに、for文ではこれは起こらないのでコンパイルは通りません。

for i in array {} // compile error

以上、ちょっとした落とし穴でした。

実装はされるか?

配列にIntoIteratorをimplしようという話は昔からありますが、まだ技術的に難しいようです。