数组的 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()
)。