遍历数组,我翻车了-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等。