迭代器答应咱们迭代一个接连的调集,例如数组、动态数组 Vec、HashMap 等,在此过程中,只需关怀调集中的元素怎么处理,而无需关怀怎么开端、怎么结束、依照什么样的索引去拜访等问题。

For循环与迭代器

迭代器与 for 循环最主要的不同就在于:是否经过索引来拜访调集。严格来说,Rust 中的 for 循环是编译器供给的语法糖,终究还是对迭代器中的元素进行遍历。在 Rust 中,完结了IntoIteratortrait 的类型都能够主动把类型调集转换为迭代器,然后经过 for 语法糖进行拜访。

例如数组:

let arr = [1, 2, 3];
for v in arr {
    peintln!("{}", v);
}

也是能够运用IntoIteratortrait的into_iter办法显式地将数组转换成迭代器。

let arr = [1, 2, 3];
for v in arr.into_iter() { 
   println!("{}", v); 
}

注:数组不是迭代器(没有完结Iterator),但是数组完结了IntoIterator,Rust 经过 for 语法糖,主动把完结了该特征的数组类型转换为迭代器,终究让咱们能够直接对一个数组进行迭代。

此外,还能够运用 for 循环对数值序列进行迭代,如

for i in 1..100 { ... };// 有限迭代器:对有限数值序列进行迭代
for i in (1..).into_iter() {...}; // 无限迭代器:无限长度的自增序列

Iteratortrait 迭代器

Iteratortrait 界说:

pub trait Iterator {
    type Item;
    fn next(&mut self) -> Option<Self::Item>;
    // 省略其余有默许完结的办法
}

在 Rust 中,迭代器是慵懒的,也便是在界说迭代器后不运用将不会发生任何事,只有在运用时迭代器才会开端迭代其间的元素。

慵懒初始化的办法确保了创立迭代器不会有任何额外的功能损耗,其间的元素也不会被耗费,只有运用到该迭代器的时分,全部才开端。

迭代器之所以成为迭代器,便是由于完结了Iteratortrait,要完结该 trait,最主要的便是要完结其间的next办法。for 循环经过不断调用迭代器上的next办法来获取迭代器中的元素。

比方:

let arr = [1, 2, 3];
let mut arr_iter = arr.into_iter();
assert_eq!(arr_iter.next(), Some(1)); 
assert_eq!(arr_iter.next(), Some(2)); 
assert_eq!(arr_iter.next(), Some(3)); 
assert_eq!(arr_iter.next(), None);

迭代器自身也能够直接调用next办法,回来值是 Option 类型(有值时是Some(T),无值时是None),遍历是依照迭代器中元素的排列顺序顺次进行的,同时手动迭代必须将迭代器声明为mut可变,由于调用next会改动迭代器其间的状况数据,运用 for 循环时则无需标注,由于for循环主动完结。

for循环是迭代器的语法糖,大约原理如下:


let values = vec![1, 2, 3];
{
    /// IntoIterator::into_iter完全限定的办法与into_iter办法values.into_iter()是等价的
    let result = match IntoIterator::into_iter(values) {
        /// 运用了loop循环配合next办法来遍历迭代器中的元素,当迭代器回来None时,跳出循环
        mut iter => loop {
            match iter.next() {
                Some(x) => { println!("{}", x); },
                None => break,
            }
        },
    };
    result
}

总归,next办法对迭代器的遍历是耗费性的,每次耗费它一个元素,终究迭代器中将没有任何元素,只能回来None

IntoIterator trait 转化成迭代器

IntoIteratortrait 界说:

trait IntoIterator
where
    <Self::IntoIter as Iterator>::Item == Self::Item, 
{
    type Item;
    type IntoIter: Iterator;
    fn into_iter(self) -> Self::IntoIter;
}

完结IntoIterator特性的类型能够被转换为迭代器。当用于for-in循环时,将主动调用该类型的into_iter办法。

比方动态 Vec 不只完结了IntoIteratortrait,&Vec&mut Vec相同如此。因而咱们能够相应的对可变与不可变的引证,以及自有值进行迭代。

Vec 完结了三种不同方式的 IntoInterator:

impl<T> IntoIterator for Vec<T>
impl<'a, T> IntoIterator for &'a Vec<T>
impl<'a, T> IntoIterator for &'a mut Vec<T>

依据被遍历目标的类型,for 循环会挑选运用哪个into_iter办法并取得不同类型的元素:

  1. 可遍历目标的同享引证回来同享引证的元素 &T
  2. 可遍历目标的可变引证回来可变引证的元素 &mut T
  3. 拥有所有权的可遍历目标回来值 T。留意:这个可遍历目标被搬运所有权,从而被会 drop 掉。
// vec = Vec<T>
for v in &vec {} // v = &T
// above example desugared
// 以上代码等价于
for v in (&vec).into_iter() {}
// vec = Vec<T>
for v in &mut vec {} // v = &mut T
// above example desugared
// 以上代码等价于
for v in (&mut vec).into_iter() {}

标准库的一揽子完结机制为迭代器Iterator自身主动完结了IntoIteratortrait:

impl<I: Iterator> IntoIterator for I {
  type Item = I::Item; 
  type IntoIter = I; 
  #[inline] 
  fn into_iter(self) -> I {
    self 
  }
}

所以vec.into_iter()vec.into_iter().into_iter().into_iter()效果相同的。

fn main() {
  let values = vec![1, 2, 3]; 
  for v in values.into_iter().into_iter().into_iter() { 
    println!("{}",v) 
  } 
}

into_iter,iter,iter_mut 发生迭代器的差异

  • into_iter()回来T,&T或 &mut T 类型的Iterator,依靠于环境;
  • iter()迭代引证(&T):调用next办法回来的类型是Some(&T)
  • iter_mut()迭代可变引证(&mut T):调用next办法回来的类型是Some(&mut T)

注:Rust命名规则:into_之类的,都是拿走所有权,_mut之类的都是可变引证,剩余的便是不可变引证。

代码示例:

fn main() {
    let values = vec![1, 2, 3];
    // 所有权搬运至迭代器中,values 将不能再运用
    for v in values.into_iter() {
        println!("{}", v)
    }
    // 下面的代码将报错,由于 values 的所有权在上面 `for` 循环中已经被搬运走
    // println!("{:?}",values);
    let values = vec![1, 2, 3];
    let _values_iter = values.iter();
    // 不会报错,由于 values_iter 仅仅借用了 values 中的元素
    println!("{:?}", values);
    let mut values = vec![1, 2, 3];
    // 对 values 中的元素进行可变借用
    let mut values_iter_mut = values.iter_mut();
    // 取出第一个元素,并修改为0
    if let Some(v) = values_iter_mut.next() {
        *v = 0;
    }
    // 输出[0, 2, 3]
    println!("{:?}", values);
}

Iterator 和 IntoIterator 的差异

Iterator是迭代器 trait,只有完结了它才干称为迭代器,才干调用next办法。

IntoIterator着重的是某一个类型如果完结了该 trait,那么该类型数据能够经过into_iter()iter()iter_mut()办法将其变成一个迭代器。

完结Iterator<Item = T>的类型能够迭代发生T类型。留意:并不存在IteratorMut类型,由于能够经过在完结Iterator特性时指定Item关联类型,来挑选其回来的是不可变引证、可变引证还是自有值。

Vec<T>办法 回来类型
.iter() Iterator<Item = &T>
.iter_mut() Iterator<Item = &mut T>
.into_iter() Iterator<Item = T>

顾客与适配器

顾客是迭代器上的办法,它会消费掉迭代器中的元素,然后回来该类型的值,这些顾客都有一个共同的特色:在界说中都依靠next办法来消费元素。

消费性适配器:某些办法(比方collectsum等)在其内部会主动调用next办法,并会拿走迭代器的所有权,耗费掉迭代器上的元素。比方sum办法,它会拿走迭代器的所有权,并重复调用next来遍历迭代器并对里边的元素进行求和,并在迭代完结时回来总和。

fn main() {
    let v1 = vec![1, 2, 3];
    let v1_iter = v1.iter();
    let total: i32 = v1_iter.sum();
    assert_eq!(total, 6);
    // v1_iter 是借用了 v1,因而 v1 能够照常运用
    println!("{:?}",v1);
    // 以下代码会报错,由于 `sum` 拿到了迭代器 `v1_iter` 的所有权
    // println!("{:?}",v1_iter);
}

迭代器适配器:Iteratortrait 中界说了另一类办法,答应咱们将当前迭代器变为不同类型的迭代器。能够链式调用多个迭代器适配器。不过由于所有的迭代器都是慵懒的,必须调用一个消费适配器办法以便获取迭代器适配器调用的结果。

let v1: Vec<i32> = vec![1, 2, 3];
v1.iter().map(|x| x + 1);  // 正告,由于map办法回来一个迭代器,map 迭代器是慵懒的,不发生任何效果
let v2: Vec<> = v1.iter().map(|x| x + 1).collect();  // 将v1中的数顺次加1然后生成一个新的Vec,其间collect()办法便是一个顾客适配器

collect办法是一个顾客适配器,能够将一个迭代器中的元素收集到指定类型中(留意:必须显式的告诉编译器想要收集成的调集类型),上面代码示例中的Vec<_>表明将迭代器中的元素收集成 Vec 类型,详细元素类型经过类型推导取得。

map办法是一个迭代器适配器,会将迭代器中的每一个值进行一系列操作,然后把该值转换成别的一个新值,该操作经过闭包完结。

let names = ["tim", "tony"];
let ages = [11, 33];
let folks: HashMap<_, _> = names.into_iter().zip(ages.into_iter()).collect();

zip是一个迭代器适配器,其作用便是将两个迭代器的内容压缩到一同,构成Iterator<Item=(ValueFromA, ValueFromB)>这样的新的迭代器,然后经过collect办法将迭代器中的(K, V)方式的值收集成HashMap<K, V>

之前的map办法中运用闭包作为迭代器适配器的参数,其最大的好处不只在于能够就地完结迭代器中元素的处理,还在于能够捕获环境值。下面的代码示例同时体现了这两个长处:

struct Shoe {
    size: u32,
    style: String,
}
fn shoes_in_size(shoes: Vec<Shoe>, shoe_size: u32) -> Vec<Shoe> {
    shoes.into_iter().filter(|s| s.size == shoe_size).collect()  //捕获外部环境中的变量 shoe_size 来与输入参数进行比较
}

filter是迭代器适配器,用于对迭代器中的每个值进行过滤。它运用闭包作为参数,该闭包从迭代器中获取一项并回来一个bool,如果闭包回来true,其值将会包含在filter供给的新迭代器中。

为自界说类型完结 Iterator trait

前面运用了数组来创立迭代器,但其实根据其它调集类型一样能够创立迭代器,也能够创立自己的迭代器——只要为自界说类型完结 Iterator trait 即可。中文标准库中的 Module std::iter 中给出了怎么完结迭代器的办法。

例如:

struct MyType {
    items: Vec<String>
}
impl MyType {
    fn iter(&self) -> impl Iterator<Item = &String> {
        MyTypeIterator {
            index: 0,
            items: &self.items
        }
    }
}
struct MyTypeIterator<'a> {
    index: usize,
    items: &'a Vec<String>
}
impl<'a> Iterator for MyTypeIterator<'a> {
    type Item = &'a String;
    fn next(&mut self) -> Option<Self::Item> {
        if self.index >= self.items.len() {
            None
        } else {
            let item = &self.items[self.index];
            self.index += 1;
            Some(item)
        }
    }
}

完结自己的迭代器非常简略,但是Iteratortrait 中,只需要自己完结next一个办法,由于其他办法都有默许完结,并且这些默许完结的办法其实都是根据next办法完结的。

比方enumerateIteratortrait 上的办法,该办法发生一个新的迭代器,其每个元素都是元素(索引,值)。enumerate是迭代器适配器,能够运用顾客迭代器或 for 循环对新发生的迭代器进行处理。

代码示例如下:

let v = vec![1u64, 2, 3, 4, 5, 6];
for (i, v) in v.iter().enumerate() {
    println!("第{}个值是{}", i, v)
}

迭代器的功能

依据测验,运用迭代器和 for 循环完结相同的任务,迭代器的运行时刻还要少一点。

迭代器是 Rust 的零本钱抽象之一,这就意味着抽象并不会引入运行时开销。

总归,迭代器是 Rust 受函数式言语启发而供给的高级言语特性,能够写出愈加简练、逻辑清晰的代码。编译器还能够经过循环展开(Unrolling)、向量化、消除边界检查等优化手法,使得迭代器和for循环都有极为高效的履行功率。

总结

Rust 中完结了Iteratortrait 才是迭代器,完结IntoIterator特性的类型能够被转换为迭代器,供给三个公共办法创立迭代器:iter()、iter_mut() 和 into_iter(),别离用于迭代 &T(引证)、&mut T(可变引证)和 T(值),其间,前两种是一般办法,而into_iter() 来自于IntoIteratortrait。

Rust 的 for 循环其实是迭代器语法糖,当没有显式的运用迭代器时,它会依据不同的上下文,别离运用 T、&T 和 &mut T 类型所完结的 into_iter() 回来的迭代器。

遍历类型回来不同遍历器的三个办法:

  • into_iter()回来 T,&T 或 &mut T 类型的 Iterator,依靠于环境,IntoIterator的办法;
  • iter()回来 Iterator<&T>,调用next办法回来的类型是Some(&T)
  • iter_mut()回来 Iterator<&mut T>,调用next办法回来的类型是Some(&mut T)

参考

  • course.rs/advance/fun…
  • kaisery.github.io/trpl-zh-cn/…
  • my_lv.gitbooks.io/rust/conten…
  • rustwiki.org/zh-CN/rust-…
  • rust.ffactory.org/std/iter/in…
  • www.becomebetterprogrammer.com/rust-iter-v…
  • github.com/pretzelhamm…