
在完成前面三篇关于 Rust 迭代器的深入学习之后,本篇文章我们尝试自己写一下 Rust 迭代器,即给我们自己的数据加上迭代器的功能。
如果有读者想复习之前的文章:
开始之前
Rust 原生的 Vec 以及数组,已经支持迭代器相关功能了:
rust
let mut a = vec![1, 2, 3];
for i in &a {
println!("{}", i);
}
a.iter_mut().for_each(|i| {
*i = 100;
});
println!("{:?}", a);
// output
// 1
// 2
// 3
// [100, 100, 100]
上述代码如果将 vec![1, 2, 3] 改成 [1, 2, 3],效果是一样的。
如果你的数据结构有基于 Vec 或者数组去实现迭代器,其实可以直接用 Vec 或者数组的迭代器,完全没有必要自己实现。
但是为了让我们更加了解迭代器的内部原理以及相关的实现细节,该篇文章我们尝试为自己的数据实现一个迭代器。
首先,你需要一个简单的类:
rust
#[derive(Debug)]
struct Rect {
a: f32,
b: f32,
c: f32,
d: f32,
}
为类实现迭代器
在 深入 Rust 迭代器(上) 文中已经知道,如果我们要为自己的数据实现一个迭代器,就需要实现 Iterator trait。
rust
impl Iterator for Rect {
type Item = f32;
fn next(&mut self) -> Option<Self::Item> {
//...
}
}
该 trait 需要实现的函数非常简单 ------ next 即可。
next 返回一个 Option,如果迭代器结束,直接返回 None。
为了能够实现迭代器功能,我们需要对 Rect 进行一点改造,添加一个迭代器下标:
rust
#[derive(Debug)]
struct Rect {
a: f32,
b: f32,
c: f32,
d: f32,
iter_index:u8, // 用于计数当前迭代的位置
}
然后,为 Rect 实现迭代器:
rust
impl Iterator for Rect {
type Item = f32;
fn next(&mut self) -> Option<Self::Item> {
let next = match self.iter_index {
0 => {
Some(self.a)
}
1 => {
Some(self.b)
}
2 => {
Some(self.c)
}
3 => {
Some(self.d)
}
_ => None,
};
self.iter_index += 1;
next
}
}
现在知道为什么 next 需要的入参是 &mut self 了吧!
每次调用 next 的时候,我们先返回当前的值,然后对迭代的下标 +1。
如果迭代结束了,则返回 None。
rust
let rect = Rect {
a: 1.0,
b: 2.0,
c: 3.0,
d: 4.0,
iter_index: 0,
};
for i in rect {
println!("{}", i);
}
// output
// 1
// 2
// 3
// 4
OK,很简单。
但是这样写,会有两个严重问题:
iter_index对迭代的影响很大,如果这个值设置错误,迭代就不能从头开始。- 无法再次使用迭代,即不能再次使用
for i in rect。
我们来看第一个问题,如果我们给 iter_index 为 3。结果就会变成:
txt
4
对,只会输出一个 4,因为我们的 next 实现,是基于 iter_index 做的。
第二个问题,当我们再次使用 rect 进行迭代的时候:

会得到这样一个错误。
原因是,for i in rect 是 Rust 提供的语法糖,实际上那段代码会被编译成如下的代码:
rust
let mut iter = IntoIterator::into_iter(collection);
loop {
match iter.next() {
Some(i) => {
println!("{}", i);
}
None => break,
}
}
查看 IntoIterator::into_iter 的源码,我们发现:
rust
#[inline]
fn into_iter(self) -> I {
self
}
IntoIterator::into_iter 会获取入参的所有权,这也就是为什么不能写两次 for i in rect 的原因。
为了解决上面两个问题,我们看看正确的迭代器是如何实现的。
IntoIterator
Rust 提供了一个 trait ------ IntoIterator,它的作用就是把一个"可迭代的东西"(比如容器)转换成一个"迭代器"。
这里,我们的 Rect 就是一个可迭代的数据,通过实现 IntoIterator 就可以变成一个迭代器了。
rust
#[derive(Debug)]
struct Rect {
a: f32,
b: f32,
c: f32,
d: f32,
// 这里去掉了 iter_index
}
struct RectIter {
data: Rect,
iter_index: u8,
}
impl Iterator for RectIter {
type Item = f32;
fn next(&mut self) -> Option<Self::Item> {
match self.iter_index {
0 => {
self.iter_index += 1;
Some(self.data.a)
}
1 => {
self.iter_index += 1;
Some(self.data.b)
}
2 => {
self.iter_index += 1;
Some(self.data.c)
}
3 => {
self.iter_index += 1;
Some(self.data.d)
}
_ => None,
}
}
}
impl IntoIterator for Rect {
type Item = f32;
type IntoIter = RectIter;
fn into_iter(self) -> Self::IntoIter {
RectIter {
data: self,
iter_index: 0,
}
}
}
读者会发现,我们对 Rect 实现了 IntoIterator,该 trait 要求返回一个 IntoIter 类型,IntoIter 实际上是 type IntoIter: Iterator<Item = Self::Item>;,也就是一个迭代器。
此处,我们定义了一个新的迭代器类型 RectIter 并实现了 Iterator,最后 IntoIterator 中的 into_iter(self) 返回 RectIter。
好的,现在我们来看看效果:
rust
let rect = Rect {
a: 1.0,
b: 2.0,
c: 3.0,
d: 4.0,
};
for i in rect {
println!("{}", i);
}
// output
// 1
// 2
// 3
// 4
我们不用再在 Rect 中定义迭代下标了,当使用迭代器的时候,RectIter 中的迭代下标,都会设置为 0 开始。
但是它依然没有解决重复使用的问题,也就是我们不能再次迭代 Rect。
迭代 &
我们要做的事情实际上很简单,就是为 &Rect 提供一个迭代器,这样我们就可以针对 &Rect 进行迭代了。
rust
// 为 &Rect 创建一个迭代器结构体(借用型)
struct RectRefIterator<'a> {
rect: &'a Rect,
index: usize,
}
// 为 RectRefIterator 实现 Iterator trait
impl<'a> Iterator for RectRefIterator<'a> {
type Item = f32;
fn next(&mut self) -> Option<Self::Item> {
let result = match self.index {
0 => Some(self.rect.a),
1 => Some(self.rect.b),
2 => Some(self.rect.c),
3 => Some(self.rect.d),
_ => None,
};
self.index += 1;
result
}
}
// 为 &Rect 实现 IntoIterator trait(借用型)
impl<'a> IntoIterator for &'a Rect {
type Item = f32;
type IntoIter = RectRefIterator<'a>;
fn into_iter(self) -> Self::IntoIter {
RectRefIterator {
rect: self,
index: 0,
}
}
}
我们直接定义让 &Rect 实现 IntoIterator trait,这里的写法比较长:impl<'a> IntoIterator for &'a Rect。
'a 是一个生命周期标注,如果你还不熟悉生命周期标注,可以先跳过,我们可以简单的理解为如果你有引用类型,需要帮助编译器来确定这个引用能存活多长时间。
此时,返回的 IntoIter 类型我们需要重新定义一个 RectRefIterator<'a>,同样,它会持有一个 &'a Rect,即 &Rect 类型。
这样,我们就可以多次遍历了。
rust
let rect = Rect {
a: 1.0,
b: 2.0,
c: 3.0,
d: 4.0,
};
for i in &rect { // 注意这里是 &rect
println!("{}", i);
}
for i in &rect {
println!("{}", i);
}
for i in &rect {
println!("{}", i);
}
如果你想消耗掉 rect,for i in rect 即可。
那么,如果我想支持 &mut 呢?
迭代 &mut
for i in &mut rect 一般这种迭代形式,是说我想在迭代的时候更改元素,例如下面这个在迭代的时候更改数组里面的值:
rust
for i in &mut a {
*i = 100;
}
好的,这应该是所有迭代器中,编写最麻烦的一个了。
我们尝试,按照上面那个写法,写一个迭代器试试:
rust
struct RectMutRefIterator<'a> {
rect: &'a mut Rect,
index: usize,
}
impl<'a> Iterator for RectMutRefIterator<'a> {
type Item = &'a mut f32;
fn next(&mut self) -> Option<Self::Item> {
let index = self.index;
self.index += 1;
match index {
0 => Some(&mut self.rect.a),
1 => Some(&mut self.rect.b),
2 => Some(&mut self.rect.c),
3 => Some(&mut self.rect.d),
_ => None,
}
}
}
其实就是把 &Rect 变成了 &mut Rect。
但是这段代码是无法通过编译的,你会得到这样一段提示

这里简单解释一下,fn next(&mut self) -> Option<Self::Item> 实际上是 fn next(&mut self) -> Option<&'a mut f32>,这个函数需要返回一个带 'a 生命周期的引用,但是 Rust 编译器不知道这个 'a 从哪儿来的,Rust 函数的生命周期标注,必须要有一个来源。
那么如果我们改成 fn next(&'a mut self) ?
也不行,因为 impl<'a> Iterator 的函数定义不是这样的,此时,似乎陷入一个僵局了···
由于这个地方过于复杂,我们不再深入探讨,我先给出用正确答案的一种:
rust
// ----------------------------------------------------
// 1. 自定义迭代器结构体
// ----------------------------------------------------
pub struct RectMutIterator<'a> {
// 指向 Rect 内部当前 f32 元素的原始可变指针。
ptr: *mut f32,
// 剩余的元素数量。
len: usize,
// 生命周期标记:将迭代器的生命周期 'a 绑定到 Rect 的 &mut 借用上。
_marker: PhantomData<&'a mut f32>,
}
// ----------------------------------------------------
// 2. 针对 &mut Rect 实现 IntoIterator
// ----------------------------------------------------
impl <'a> IntoIterator for &'a mut Rect {
type Item = &'a mut f32;
type IntoIter = RectMutIterator<'a>;
fn into_iter(self) -> Self::IntoIter {
let ptr_f32 = self as *mut Rect as *mut f32;
RectMutIterator {
ptr: ptr_f32,
len: 4, // 字段总数
_marker: PhantomData,
}
}
}
// ----------------------------------------------------
// 3. 迭代器的实现
// ----------------------------------------------------
impl<'a> Iterator for RectMutIterator<'a> {
type Item = &'a mut f32; // 返回 f32 的可变引用
fn next(&mut self) -> Option<Self::Item> {
// 1. 检查是否迭代完毕
if self.len == 0 {
return None;
}
// 2. 减少剩余元素计数
self.len -= 1;
// 3. 核心 unsafe 逻辑
unsafe {
// 获取当前元素的指针
let current_ptr = self.ptr;
// 将内部指针移动到下一个元素
// offset(1) 是一个 safe 抽象,但它内部执行的是 unsafe 指针算术
// 它确保 `self.ptr` 现在指向下一个 f32 (即下一个字段)
self.ptr = self.ptr.offset(1);
// 4. 将当前的原始指针转换回安全的 &mut f32 引用
// 这是安全的,因为我们通过 len 和 ptr 的管理,
// 确保了当前元素是独占的,且生命周期被正确绑定。
Some(&mut *current_ptr)
}
}
}
我们测试一下看看:
rust
let mut rect = Rect {
a: 1.0,
b: 2.0,
c: 3.0,
d: 4.0,
};
for i in &mut rect {
*i += 10.0;
}
println!("{:?}", rect);
// output
// Rect { a: 11.0, b: 12.0, c: 13.0, d: 14.0 }
结果是正确的。
好的,到此,几种基本的迭代器类型我们已经实现完毕了。除了 &mut 比较复杂以外,其他两种还算是轻松。
Rust 的一些习惯
实际上,Rust 中针对迭代器,还有一些习惯,我们在 深入 Rust 迭代器(上) 提到 Rust 迭代器中,有三种等价情况:
for i in &vec等价于for i in vec.iter()。for i in &mut等价于for i in vec.iter_mut()。for i in vec等价于for i in vec.into_iter()。
所以,我们给 Rect 添加一下这三个方法:
rust
impl Rect {
fn iter_mut(&mut self) -> RectMutIterator {
RectMutIterator {
ptr: self as *mut Rect as *mut f32,
len: 4,
_marker: PhantomData,
}
}
fn iter(&self) -> RectRefIterator {
RectRefIterator {
rect: self,
index: 0,
}
}
fn into_iter(self) -> RectIter {
RectIter {
data: self,
iter_index: 0,
}
}
}
这样,我们就可以使用迭代器适配器方法和管道了:
rust
let mut rect = Rect {
a: 1.0,
b: 2.0,
c: 3.0,
d: 4.0,
};
rect.iter_mut().for_each(|i| {
*i += 10.0;
});
rect.iter().for_each(|a| print!("{:?} ", a));
println!();
rect.into_iter().for_each(|a| print!("{:?} ", a));
println!();
// output
// 11.0 12.0 13.0 14.0
// 11.0 12.0 13.0 14.0
倒序
在 深入 Rust 迭代器(下) 中,我们最后提到一个 rev(),也就是迭代器倒序。这里我们也实现一下,这样你就能明白那篇文章中,为什么输出结果是那样的了。
我们仅针对 &Rect 实现倒序。
为了实现倒序,我们需要给 RectRefIterator 添加一个 end 字段,该字段和 index 其实效果是一样的,用于标识当前应该返回哪个数据。
rust
struct RectRefIterator<'a> {
rect: &'a Rect,
index: usize,
end: usize,
}
接下来在所有用到 RectRefIterator 的地方,设置 end 为 4:
rust
RectRefIterator {
rect: self,
index: 0,
end: 4,
}
为了能够让迭代器支持 rev(),我们需要实现一个 DoubleEndedIterator 的 trait,该 trait 只需要实现 fn next_back(&mut self) 即可,即倒着返回数据。
rust
impl <'a> DoubleEndedIterator for RectRefIterator<'a> {
fn next_back(&mut self) -> Option<Self::Item> {
let result = match self.end {
1 => Some(self.rect.a),
2 => Some(self.rect.b),
3 => Some(self.rect.c),
4 => Some(self.rect.d),
_ => return None,
};
self.end -= 1;
result
}
}
好,万事俱备,我们测试一下:
rust
let mut rect = Rect {
a: 1.0,
b: 2.0,
c: 3.0,
d: 4.0,
};
rect.iter().rev().for_each(|a| print!("{:?} ", a));
// output
// 4.0 3.0 2.0 1.0
完美。
总结
好的,关于 Rust 迭代器的这个系列,终于迎来了完结。
我们在最后这篇文章中,给自己的数据实现了迭代器功能。
通过我们手动编写迭代器,我们不仅学会了如何实现迭代器,也加深了对 Rust 中数据使用的理解,你会发现,Rust 始终围绕所有权 、借用 、可变借用来实现数据的访问。
这种设计不是限制,而是一种引导------它迫使我们在编写代码的时候就思考:
- 谁拥有数据?
- 谁在读取或修改它?
- 生命周期是否安全?
正是这些看似"严格"的规则,让 Rust 能在不依赖垃圾回收的情况下,既保证内存安全,又实现零成本抽象。而迭代器,正是这一哲学的完美体现:它把控制权交给开发者,又用类型系统默默守护着每一份引用的安全。
现在,当你再看到 for x in vec 时,或许不再只觉得它简洁优雅,更能体会到背后那套精密、可靠、充满智慧的机制在默默运转。
感谢你一路走到这里。愿你在 Rust 的旅程中,继续享受这种"被约束的自由"。