C 程序员写遍历,第一反应通常是索引或指针:
for (size_t i = 0; i < len; i++) {
sum += data[i];
}
或者:
for (uint8_t *p = buf; p < buf + len; p++) {
*p ^= key;
}
Rust 当然也能写循环,但更常用的模型是 iterator。它不是“高级语法糖”,而是把遍历过程变成一个可以传递、组合、消费的值。
let sum: u32 = data.iter().map(|b| *b as u32).sum();
这一篇只讲 C 程序员最需要的工程直觉:怎样从索引循环迁移到 iterator,什么时候借用,什么时候可变借用,什么时候消耗容器。
for 遍历的是 iterator
Rust 的 for 不是 C 的三段式 for。它遍历一个实现了 IntoIterator 的值:
let data = vec![1_u8, 2, 3];
for byte in &data {
println!("{byte}");
}
这里的 &data 会产生只读 iterator。每次循环拿到一个元素引用。
如果只想按索引访问,Rust 也可以写:
for i in 0..data.len() {
println!("{}", data[i]);
}
但普通遍历优先写成:
for byte in &data {
println!("{byte}");
}
这样少写索引,也少一次越界风险。Rust 的索引越界会 panic,iterator 会自然按元素范围遍历。
iter、iter_mut、into_iter 的区别
对 C 程序员来说,最先要分清三个入口:
iter() -> 只读借用每个元素
iter_mut() -> 可变借用每个元素
into_iter() -> 消耗容器或值,拿走元素
只读遍历:
let data = vec![1_u8, 2, 3];
for byte in data.iter() {
println!("{byte}");
}
println!("{}", data.len());
byte 是 &u8,data 仍然可用。
原地修改:
let mut data = vec![1_u8, 2, 3];
for byte in data.iter_mut() {
*byte ^= 0xAA;
}
byte 是 &mut u8。循环期间,iterator 逐个给你元素的可变借用。
消耗容器:
let data = vec![String::from("a"), String::from("b")];
for item in data.into_iter() {
println!("{item}");
}
// data 不能再用
item 是 String,所有权被拿出来。容器被消费。
这个区别和前面讲所有权、借用、可变借用是一条线:遍历不是例外,它也要回答“谁拥有元素,谁只是看,谁在改”。
map 和 filter 适合表达数据转换
C 里常见写法是手动创建输出数组,再循环填充:
size_t out_len = 0;
for (size_t i = 0; i < len; i++) {
if (items[i] > 0) {
out[out_len++] = items[i] * 2;
}
}
Rust 可以写成 iterator 组合:
let out: Vec<i32> = items
.iter()
.filter(|value| **value > 0)
.map(|value| *value * 2)
.collect();
filter 负责筛选,map 负责转换,collect 把结果收集成 Vec。
这里 items.iter() 产生的是 &i32,所以闭包里会看到引用。也可以先用 copied() 把简单值复制出来:
let out: Vec<i32> = items
.iter()
.copied()
.filter(|value| *value > 0)
.map(|value| value * 2)
.collect();
对 Copy 的小整数来说,copied() 很常见,能减少闭包里到处写 *value。
collect 决定最终容器
iterator 是惰性的。单独写 map 不会立刻生成新容器:
let iter = data.iter().map(|value| value * 2);
只有被消费时才真正运行。常见消费方式有:
let values: Vec<i32> = iter.collect();
或者直接求和:
let sum: i32 = data.iter().sum();
collect 的目标类型很重要。同一个 iterator 可以收集成不同容器:
let values: Vec<i32> = data.iter().copied().collect();
let set: std::collections::HashSet<i32> = data.iter().copied().collect();
这和 C 里“循环逻辑”和“输出容器”通常绑在一起不一样。Rust 会把“怎么遍历”和“收集到哪里”拆开。
enumerate 替代手写索引
如果既要元素又要下标,用 enumerate:
for (index, byte) in data.iter().enumerate() {
println!("{index}: {byte}");
}
这比手写:
for index in 0..data.len() {
let byte = data[index];
}
更少出错。索引和元素来自同一个 iterator,不需要自己维护同步关系。
处理协议字段、错误位置、行号时,enumerate 很常用:
fn find_zero(data: &[u8]) -> Option<usize> {
for (index, byte) in data.iter().enumerate() {
if *byte == 0 {
return Some(index);
}
}
None
}
也可以写成:
fn find_zero(data: &[u8]) -> Option<usize> {
data.iter().position(|byte| *byte == 0)
}
两种都可以。初学时先写 for 循环,读起来更直接;熟悉以后再用更短的组合器。
不要为了炫技写难懂链式调用
iterator 很强,但不是每段代码都要写成一长串链式调用。
下面这种写法可能太密:
let result: Vec<_> = packets
.iter()
.filter(|packet| packet.enabled)
.flat_map(|packet| packet.fields.iter())
.filter_map(|field| parse_field(field).ok())
.filter(|field| field.len() > 0)
.map(|field| field.normalize())
.collect();
如果每一步都有业务含义,可以拆开:
let enabled_packets = packets.iter().filter(|packet| packet.enabled);
let fields = enabled_packets.flat_map(|packet| packet.fields.iter());
let result: Vec<_> = fields
.filter_map(|field| parse_field(field).ok())
.filter(|field| field.len() > 0)
.map(|field| field.normalize())
.collect();
或者直接写 for。Rust 里清楚比“函数式味道足”更重要。
iterator 和错误处理
处理 Result 时,iterator 也能很好配合。
假设每行都要解析:
fn parse_line(line: &str) -> Result<u32, ParseError> {
line.parse().map_err(|_| ParseError)
}
可以把 Iterator<Item = Result<T, E>> 收集成 Result<Vec<T>, E>:
let values: Result<Vec<u32>, ParseError> =
lines.iter().map(|line| parse_line(line)).collect();
只要中间有一个 Err,结果就是 Err;全部成功才得到 Vec<u32>。
如果逻辑更复杂,for 加 ? 也很清楚:
let mut values = Vec::new();
for line in lines {
values.push(parse_line(line)?);
}
不要强迫自己把所有错误路径都塞进链式调用。库代码里,可读性通常更重要。
自己的类型也可以提供 iterator
如果你写的是库,类型内部可能有 Vec、数组或其他集合。不要把内部字段直接暴露出去,可以提供 iterator 方法:
struct Packet {
fields: Vec<Field>,
}
impl Packet {
fn fields(&self) -> impl Iterator<Item = &Field> {
self.fields.iter()
}
}
调用方可以:
for field in packet.fields() {
println!("{}", field.name());
}
如果允许修改:
impl Packet {
fn fields_mut(&mut self) -> impl Iterator<Item = &mut Field> {
self.fields.iter_mut()
}
}
这比返回内部 Vec 更稳妥。API 暴露的是“可以遍历字段”,不是“内部一定用 Vec 存字段”。
这一篇先记住什么
iterator 是 Rust 里表达遍历的核心工具。它不是为了替代所有 for,而是让遍历、过滤、转换和收集变得可组合。
从 C 迁移时先抓住这些对应关系:
for i in 0..len -> 优先考虑 for item in slice
只读遍历 -> iter() / &collection
原地修改 -> iter_mut()
拿走元素 -> into_iter()
手写索引计数 -> enumerate()
筛选和转换 -> filter() / map()
生成 Vec 或其他容器 -> collect()
遍历中可能失败 -> for + ? 或 collect::<Result<Vec<_>, _>>()
iterator 不要求你放弃普通循环。更实际的标准是:简单遍历用 for,清晰的数据管道用组合器,复杂业务逻辑拆开写。
下一篇讲 closure 和回调。C 里常用函数指针加 void *ctx 表达回调,Rust 会用 closure 把函数和捕获环境放在一起。