配列に対する IntoIterator

概要

  • すべてのエディションで、配列が IntoIterator を実装するようになります。
  • Rust 2015 と Rust 2018 では、メソッド呼び出し構文が使われても(つまり array.into_iter() と書いても)、 IntoIterator::into_iter隠されています。 これにより、array.into_iter() は従来どおり (&array).into_iter() に解決されます。
  • Rust 2021 から、array.into_iter()IntoIterator::into_iter を意味するように変更されます。

詳細

Rust 1.53 より前は、配列の参照だけが IntoIterator を実装していました。 すなわち、&[1, 2, 3]&mut [1, 2, 3] に対しては列挙できる一方で、[1, 2, 3] に対して列挙することはできませんでした。

for &e in &[1, 2, 3] {} // Ok :)
                        // OK :)

for e in [1, 2, 3] {} // Error :(
                      // エラー :(

これは古くからある Issueですが、見た目ほど解決は簡単ではありません。 トレイト実装を追加するだけでは、既存のコードが壊れてしまいます。 メソッド呼び出し構文の仕組み上、array.into_iter() は現状でも (&array).into_iter() とみなされてコンパイルが通ります。 トレイト実装を追加すると、その意味が変わってしまうのです。

多くのケースで、この手の互換性破壊(トレイト実装の追加)は「軽微」で許容可能とみなされてきました。 しかし、このケースではあまりにも多くのコードが壊れてしまうのです。

何度も提案されてきたのは、「Rust 2021 でのみ配列に IntoIterator を実装する」ことでした。 しかし、これは単に不可能なのです。 エディションは併用されうるので、あるエディションではトレイト実装が存在して、別のエディションでは存在しない、というわけにはいかないからです。

代わりに、(Rust 1.53.0 から)トレイト実装はすべてのエディションで追加されましたが、 Rust 2021 より前のコードが破壊されないようにちょっとしたハックが行われました。 Rust 2015 と 2018 のコードでは、コンパイラは従来どおり array.into_iter()(&array).into_iter() に解決し、あたかもトレイト実装が存在しないかのように振る舞います。 これは .into_iter() というメソッド呼び出し構文だけに適用されます。 一方、このルールは for e in [1, 2, 3], iter.zip([1, 2, 3]), IntoIterator::into_iter([1, 2, 3]) といった他の構文には適用されず、 そのような書き方は全てのエディションで使えるようになります。

互換性破壊を防ぐためにちょっとしたハックが必要になったのは残念ですが、 これによりエディション間の違いが最小限になったのです。

移行

into_iter() への呼び出しのうち、Rust 2021 で意味が変わるようなものに対しては、 array_into_iter というリントが発生します。 1.41 のリリース以降、array_into_iter リントはすでにデフォルトで警告として発出されています(1.55 ではさらにいくつかの機能追加が行われました)。 警告が今現在出ていないコードは、今すぐにでも Rust 2021 に進むことができます!

コードを自動的に Rust 2021 エディションに適合するよう自動移行するか、既に適合するものであることを確認するためには、以下のように実行すればよいです:

cargo fix --edition

エディション間の違いが少ないので、Rust 2021 への移行も非常に簡単です。

配列に対する into_iter のメソッド呼び出しに関しては、(イテレータの)要素が参照でなく所有権を持った値となります。

例えば:

fn main() {
  let array = [1u8, 2, 3];
  for x in array.into_iter() {
    // x is a `&u8` in Rust 2015 and Rust 2018
    // x is a `u8` in Rust 2021
    // Rust 2015 と Rust 2018 では、x は `&u8`
    // Rust 2021 では、x は `u8`
  }
}

移行のための最も簡単な方法は、前のエディションと完全に同じ挙動をするように、 所有権を持った配列上を参照でイテレートするもう一つのメソッド iter() を呼び出すことです:

fn main() {
  let array = [1u8, 2, 3];
  for x in array.iter() { // <- This line changed
                          //    この行を書き換えた
    // x is a `&u8` in all editions
    // x はすべてのエディションで `&u8`
  }
}

必須でない移行

前のエディションで完全修飾メソッド構文を使っていた場合(例: IntoIterator::into_iter(array))、 これはメソッド呼び出し構文に書き換え可能です(例: array.into_iter())。