前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >遍历数组,我翻车了-Rust

遍历数组,我翻车了-Rust

原创
作者头像
江湖安得便相忘
修改2019-11-06 10:26:43
2.8K0
修改2019-11-06 10:26:43
举报

其他的多数语言中的, 数组直接就是可迭代的,无论是下标遍历还是迭代器迭代,都可以运行,所以刚开始用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);
}

可是在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等。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档