数组的 IntoIterator

摘要

  • 数组在所有版本中都实现了 IntoIterator
  • 当使用方法调用语法(即 array.into_iter())时,对 IntoIterator::into_iter 的调用在 Rust 2015 和 Rust 2018 中是隐藏的。因此,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 :)

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

这已经是一个长期存在的问题,但解决方案并不像看起来那么简单。仅仅添加 trait 实现会破坏现有代码。由于方法调用语法的工作方式array.into_iter() 今天已经可以编译,因为它隐式地调用 (&array).into_iter()。添加 trait 实现会改变其含义。

通常,这种类型的破坏(添加 trait 实现)被归类为“次要”且可以接受。但在这种情况下,会有太多的代码被它破坏。

人们多次建议“仅在 Rust 2021 中为数组实现 IntoIterator”。然而,这根本不可能。你不能让一个 trait 实现在一个版本中存在而在另一个版本中不存在,因为版本是可以混合的。

相反,trait 实现在所有版本中都被添加(从 Rust 1.53.0 开始),但有一个小技巧可以避免在 Rust 2021 之前出现破坏。在 Rust 2015 和 2018 的代码中,编译器仍然会将 array.into_iter() 解析为 (&array).into_iter(),就像以前一样,就好像 trait 实现不存在一样。这适用于 .into_iter() 方法调用语法。它不影响任何其他语法,例如 for e in [1, 2, 3]iter.zip([1, 2, 3])IntoIterator::into_iter([1, 2, 3])。这些将在所有版本中开始工作。

虽然为了避免破坏而需要一个小技巧令人遗憾,但这种解决方案将版本之间的差异保持在绝对最小值。

迁移

每当有 into_iter() 的调用在 Rust 2021 中会改变含义时,都会触发一个 lint,array_into_iter。自 1.41 版本以来(在 1.55 中进行了一些增强),array_into_iter lint 在所有版本中默认都是警告。如果你的代码已经没有警告,那么它应该已经为 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 2021 中迁移的最直接方法是通过调用 iter() 来保持与先前版本完全相同的行为,该方法还通过引用迭代拥有的数组。

fn main() {
  let array = [1u8, 2, 3];
  for x in array.iter() { // <- This line changed
    // x is a `&u8` in all editions
  }
}

可选迁移

如果你在之前的版本中使用完全限定的方法语法(即 IntoIterator::into_iter(array)),则可以将其升级为方法调用语法(即 array.into_iter())。