遍历数组,我翻车了-Rust
- 2019 年 11 月 12 日
- 筆記
其他的多数语言中的, 数组直接就是可迭代的,无论是下标遍历还是迭代器迭代,都可以运行,所以刚开始用Rust的时候就翻车了。
像这样子:
let arr = [1,2,3]; for i in arr { println!("{}", i); }
好在Rust的编译器会详细的告诉我:
error[E0277]: `[{integer}; 3]` is not an iterator --> src/main.rs:20:14 | 20 | for i in arr { | ^^^ borrow the array with `&` or call `.iter()` on it to iterate over it | = help: the trait `std::iter::Iterator` is not implemented for `[{integer}; 3]` = note: arrays are not iterators, but slices like the following are: `&[1, 2, 3]` = note: required by `std::iter::IntoIterator::into_iter`
数组是不能直接使用for进行迭代的,必须使用数组引用或者获取数组的迭代器。因为for只能对实现了迭代器(std::iter::Iterator)trait的类型遍历。
通过错误信息中不难看出,Rust给出的建议是使用数组引用或者调用iter()方法来使数组获得迭代器能力。
在探讨这上述两种形式之前来认识两个trait。
std::iter::IntoIterator 和 std::iter::Iterator
IntoIterator
pub trait IntoIterator { type Item; type IntoIter: Iterator<Item=Self::Item>; fn into_iter(self) -> Self::IntoIter; }
其中含有两个类型定义,一个方法,主要功能获取一个迭代器,在for中,会自动使用std::iter::Iterator::into_iter()来获取类型的迭代器。
Iterator
pub trait Iterator { type Item; fn next(self) -> Option<Self::Item>; //...其他方法 }
迭代器trait,实现此trait后即可使用for迭代,当next()返回None时则停止迭代。
了解了两个trait的作用后下面来探讨两种数组迭代形式:
数组引用形式:
let arr: [i32; 10] = [1; 10]; for i in &arr { //loop body }
&arr的类型是&[i32; 10],标准库libcore/array.rs中对数组引用实现了IntoIterator。
impl<'a, T, const N: usize> IntoIterator for &'a [T; N] where [T; N]: LengthAtMost32, { type Item = &'a T; type IntoIter = Iter<'a, T>; fn into_iter(self) -> Iter<'a, T> { self.iter() } }
IntoIterator返回了一个Iter结构体。
pub struct Iter<'a, T: 'a> { ptr: *const T, end: *const T, // If T is a ZST, this is actually ptr+len. This encoding is picked so that // ptr == end is a quick test for the Iterator being empty, that works // for both ZST and non-ZST. _marker: marker::PhantomData<&'a T>, }
Iter<T>结构体通过 iterator! 宏实现了Iterator(位于libcore/slice/mod.rs中),所以通过for可以直接进行迭代。
但是数组引用的迭代方式有一个限制。
上面数组引用的实现IntoIterator中有一个trait bound,LengthAtMost32:从名称上可以看出是长度最大为32。
LengthAtMost32是利用array_impl! 宏进行实现。
macro_rules! array_impls { ($($N:literal)+) => { $( #[unstable(feature = "const_generic_impls_guard", issue = "0")] impl<T> LengthAtMost32 for [T; $N] {} )+ } } array_impls! { 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 }
通过这个宏为多个数组类型实现LengthAtMost32。也就是说数组引用的形式最多支持到[T; 32]类型。
let arr = [1; 33]; for i in &arr { println!("{}", i); }
以上的代码编译不通过,因为[T; 33]并未实现LengthAtMost32,所以&[T; 33]及长度更大的数组类型就没有实现IntoIterator,这也就是数组引用的形式的一个限制。
error[E0277]: the trait bound `&[{integer}; 33]: std::iter::IntoIterator` is not satisfied --> src/main.rs:29:14 | 29 | for i in &a { | ^^ the trait `std::iter::IntoIterator` is not implemented for `&[{integer}; 33]` | = help: the following implementations were found: <&'a [T; _] as std::iter::IntoIterator> <&'a [T] as std::iter::IntoIterator> <&'a mut [T; _] as std::iter::IntoIterator> <&'a mut [T] as std::iter::IntoIterator> = note: required by `std::iter::IntoIterator::into_iter`
调用iter()方法:
既然数组引用有限制,那么就改成iter()
let arr = [1; 33]; for i in arr.iter() { println!("{}", i); }
arr = [1; 33];for i in arr.iter() { println!("{}", i);}可是在libcore/array.rs中并没有对[T; N]实现iter()方法。通过IDE的定义跳转可以找到iter()是libcore/slice/mod.rs中,对slice [T] 类型实现的方法。这一点在std/primitive.array.html中有提到:当数组调用slice方法时会强制转换为slice类型,反之则不会,因为slice是动态的大小,数组是固定大小,不能由slice转换到数组。
所以arr.iter()相当于(arr[..]).iter() 或 (&a[..]).iter()。
iter()方法在libcore/slice/mod.rs定义
impl<T> [T] { //... pub fn iter(&self) -> Iter<'_, T> { unsafe { let ptr = self.as_ptr(); assume(!ptr.is_null()); let end = if mem::size_of::<T>() == 0 { (ptr as *const u8).wrapping_add(self.len()) as *const T } else { ptr.add(self.len()) }; Iter { ptr, end, _marker: marker::PhantomData } } } //... }
同样返回Iter<T>结构体,与数组引用实现的IntoIterator返回的迭代器一致。
总结:
– 数组由于没有实现迭代器,而不能实现for遍历,但是可以使用loop或者while通过下标遍历。
– 通过数组引用进行遍历,存在最大长度限制,超过32就无法直接遍历了。
– 建议使用iter()方法获取Iterator迭代器,Iterator迭代器中包含很多方法,位于libcore/iter/traits/iterator.rs,而且Iter<T>还实现了许多其他的功能,例如DoubleEndedIterator等。