前言

一般来说,Rust 里定义了一个结构 struct,是不会给他实现 Iterator trait 的,如果希望能够支持 for in 遍历,需要实现 Iterator trait。 在 Rust 中通常会定义一个 iter() 方法,转换成另一个实现了 Iterator trait 的结构。

这里我们简单实现一个,并讲讲这个过程中 Rust 里特有的语法,概念等。

Implementation

简单定义一个 struct,里面存放着 0 到 9 这是个数字,看起来写得很脱裤子放屁,用来演示而已:

struct Nums {
    values: [usize; 10],
}

impl Nums {
    pub fn new() -> Self {
        let mut arr = [0; 10];
        let mut n = 0;
        for v in arr.iter_mut() {
            *v = n;
            n += 1;
        }
        Self { values: arr }
    }
}

这个 Nums 是无法在 for in 里遍历的:

fn main() {
    let n = Nums::new();
    for v in n {
        println!("{}", v);
    }
}

编译器会抱怨:

`Nums` is not an iterator
the trait `Iterator` is not implemented for `Nums`
required for `Nums` to implement `IntoIterator`

根据提示需要实现 Iterator trait,按照我们一开始说的,倾向于调用 iter() 方法转换成另一个实现了 Iterator 的结构:

impl Nums {
    pub fn iter(&self) -> NumsIter {
        NumsIter { cur: 0, num: self }
    }
}

这里 iter 方法返回了一个 NumsIter 结构,它的定义就像这样:

struct NumsIter<'a> {
    cur: usize,
    num: &'a Nums,
}

为什么不直接实现?

在 Nums 直接实现 Iterator 不行吗? 可以,但是遍历的功能我们会保留一个变量,用来记录上次的值,如果直接在 Nums 上实现,那么就需要多这样一个变量:

struct Nums {
		cur: usize, // 用于记录上次遍历的值
    values: [usize; 10],
}

可是这个变量 cur 只有在遍历的时候在用得到对吧,不遍历的时候根本用不到,我不想在我不需要的时候有这个变量来污染我的结构,还是放在 NumsIter 里吧。

生命周期

在 NumsIter 结构里会看到这个玩意儿:<'a>,这坨玩意儿是 Rust 特有(咬牙切齿)的生命周期标注。 因为 Rust 里所有权的存在,当一个变量赋予给了另一个变量,只要没有实现 Copy trait,先前变量的所有权就转移到了别的变量里面了,先前变量就无法在使用:

let a = String::new();
let b = a;
println!("{}", a); 
// 编译器抱怨 borrow of moved value: `a`
// value borrowed here after move

这里 'a' 赋值给了 'b','a' 的所有权就给了 'b',变量 'a' 不能再使用。

我们的 NumsIter 里需要遍历 Nums 里的值,就需要拿到 Nums 的数据,可是如果将 Nums 的所有权给 NumsIter 的话,遍历完了后 Nums 就无法使用了,也是非常不便的。 所幸的是不需要持有 Nums 的所有权,而是临时借用一下 Nums。 在 NumsIter 的属性定义里的这个属性:num: &'a Nums& 符号代表借用一下。你可以类比一下 C 里面的指针,或者引用。

可是这样又带来了另一个问题:我 NumsIter 借用了 Nums,可是如果我 NumsIter 还在借用的时候 Nums 释放了怎么办?悬垂指针很熟悉吧~

这就需要生命周期检查了!也就是 num: &'a Nums 里的 'a

<'a>

由单引号标注在左侧的泛型是生命周期标注,就是一个用于生命周期标注的泛型,就跟普通的泛型一样,使用前需要先声明:struct NumsIter<'a> {},使用时标注在变量的借用符号 & 左侧:num: &'a Nums,表示变量 num 借用的对象所存在的时间不得少于 'a 所标注的时间。

怎么理解呢?

需要整体来看,NumsIter 的定义:

struct NumsIter<'a> {
    cur: usize,
    num: &'a Nums,
}

NumsIter 定义了生命周期标注 'a,意味着 'a 生命周期和 NumsIter 一样长:

fn main() {
	
	let t = NumsIter {..., ...};
}

那么就存在一个生命周期 'a 和 变量 t 同声同死:

fn main() {
	
	                                       'a --
	let t = NumsIter {..., ...};               |
	                                          --
}

然后属性里 num: &'a Nums 表示借用的 Nums 存活的时间必须不小于(outlive) 'a。所以我们这样写是合法的:

let v = Nums::new();
let t = NumsIter {0, v};

他们的生命周期是这样的:

fn main() {
	  
		let v = Nums::new();                v -------
	                                       'a --    |
		let t = NumsIter {0, v};               |    |
	                                          --    |
		                                        -----
}

变量 v 存活的时间超过 NumsIter 定义的生命周期 'a,编译通过。这样一来,就可以保证在 NumsIter 借用 Nums 的时间内 Nums 都是存在的。

实现 Iterator

给我们的 NumsIter 实现 Iterator trait 以支持遍历。

impl<'a> Iterator for NumsIter<'a> {
    type Item = usize;

    fn next(&mut self) -> Option<Self::Item> {
				 todo!();
    }
}

实现 trait 的语法:impl trait for struct {} 我们又看到了一个声明周期标注 'a,还是一个意思,给 NumsIter 实现 trait 需要生命周期标注,就跟泛型一样。

这个 type Item = usize; 是什么?关联类型。额,其实就是泛型。 那为什么写成关联类型,因为如果泛型太多的话写在尖括号里写不下。<T, J, K, .......>,写成关联类型就好看多了。

在 Iterator 里面的关联类型 Item 指示 next 方法里的返回类型。 实现后是这样的,非常古典的写法。

impl<'a> Iterator for NumsIter<'a> {
    type Item = usize;

    fn next(&mut self) -> Option<Self::Item> {
        if self.cur < self.num.values.len() {
            let res = Some(self.num.values[self.cur]);
            self.cur += 1;
            res
        } else {
            None
        }
    }
}

完整代码

实现完了后就可以在 for in 里调用了:

fn main() {
    let n = Nums::new();
    for v in n.iter() {
        println!("{}", v);
    }
}

完整代码:

fn main() {
    let n = Nums::new();
    for v in n.iter() {
        println!("{}", v);
    }
}

struct Nums {
    values: [usize; 10],
}

impl Nums {
    pub fn new() -> Self {
        let mut arr = [0; 10];
        let mut n = 0;
        for v in arr.iter_mut() {
            *v = n;
            n += 1;
        }
        Self { values: arr }
    }

    pub fn iter(&self) -> NumsIter {
        NumsIter { cur: 0, num: self }
    }
}

struct NumsIter<'a> {
    cur: usize,
    num: &'a Nums,
}

impl<'a> Iterator for NumsIter<'a> {
    type Item = usize;

    fn next(&mut self) -> Option<Self::Item> {
        if self.cur < self.num.values.len() {
            let res = Some(self.num.values[self.cur]);
            self.cur += 1;
            res
        } else {
            None
        }
    }
}

Con

嘿!你还别说,小小一段 Rust 需要了解的东西还挺多的。