Rust 迭代器
惰性求值、适配器与消费者、自定义迭代器、以及性能。
Key Points适配器(`map`/`filter`)是惰性的,只有消费者(`collect`/`sum`)才真正执行。
惰性求值
Rust
fn main() {
let v = vec![1, 2, 3, 4, 5];
// 这里什么都没算
let iter = v.iter().map(|x| {
println!("计算 {}", x);
x * 2
});
println!("--- 还没开始 ---");
// 消费者触发执行
let doubled: Vec<i32> = iter.collect();
println!("{:?}", doubled);
}Output
--- 还没开始 ---
计算 1
计算 2
计算 3
计算 4
计算 5
[2, 4, 6, 8, 10]Adapters only describe work and execute nothing. Two benefits: chains compose without intermediate collections, and the compiler fuses the whole chain into one loop.
常用适配器
| Method | Effect |
|---|---|
map | 转换每个元素 |
filter | 保留满足条件的 |
filter_map | 转换 + 过滤 Option |
flat_map | 转换并展平 |
take / skip | 取前 n 个 / 跳过前 n 个 |
take_while | 取到条件不成立为止 |
chain | 拼接两个迭代器 |
zip | 配对 |
enumerate | 加上下标 |
rev | 反转 |
peekable | 可预读下一个 |
Rust
fn main() {
let v = vec![1, 2, 3, 4, 5, 6];
let r: Vec<i32> = v.iter()
.filter(|&&x| x % 2 == 0)
.map(|&x| x * x)
.collect();
println!("{:?}", r);
for (i, x) in v.iter().enumerate().take(3) {
print!("{}:{} ", i, x);
}
println!();
}Output
[4, 16, 36]
0:1 1:2 2:3 消费者
Rust
fn main() {
let v = vec![1, 2, 3, 4, 5];
println!("{}", v.iter().sum::<i32>());
println!("{}", v.iter().product::<i32>());
println!("{:?}", v.iter().max());
println!("{:?}", v.iter().min());
println!("{}", v.iter().count());
println!("{}", v.iter().any(|&x| x > 4));
println!("{}", v.iter().all(|&x| x > 0));
println!("{:?}", v.iter().find(|&&x| x % 3 == 0));
println!("{:?}", v.iter().position(|&x| x == 3));
let s: String = v.iter().map(|x| x.to_string()).collect::<Vec<_>>().join(",");
println!("{}", s);
}Output
15
120
Some(5)
Some(1)
5
true
true
Some(3)
Some(2)
1,2,3,4,5自定义迭代器
Rust
struct Counter {
count: u32,
max: u32,
}
impl Counter {
fn new(max: u32) -> Self {
Counter { count: 0, max }
}
}
impl Iterator for Counter {
type Item = u32;
fn next(&mut self) -> Option<Self::Item> {
if self.count < self.max {
self.count += 1;
Some(self.count)
} else {
None
}
}
}
fn main() {
let v: Vec<u32> = Counter::new(5).collect();
println!("{:?}", v);
let sum: u32 = Counter::new(4).filter(|x| x % 2 == 0).sum();
println!("{}", sum);
}Output
[1, 2, 3, 4, 5]
6Implementing Iterator needs only next(); the dozens of other methods are default implementations you get for free. One of the prettiest uses of trait defaults.
性能
Rust
// 这两种写法编译后的机器码基本相同
let sum1: i32 = (1..=100).filter(|x| x % 2 == 0).sum();
let mut sum2 = 0;
for x in 1..=100 {
if x % 2 == 0 {
sum2 += x;
}
}"Zero-cost abstraction" shows up here: the chain is inlined and fused into a single loop with no call overhead and no intermediate Vec. So do not avoid iterators for performance.
iter() borrows (&T), into_iter() consumes and yields T, iter_mut() yields &mut T. for x in v is into_iter() — which is why it moves v.惰性求值
Rust
fn main() {
let v = vec![1, 2, 3, 4, 5];
// 这里什么都没算
let iter = v.iter().map(|x| {
println!("计算 {}", x);
x * 2
});
println!("--- 还没开始 ---");
// 消费者触发执行
let doubled: Vec<i32> = iter.collect();
println!("{:?}", doubled);
}Output
--- 还没开始 ---
计算 1
计算 2
计算 3
计算 4
计算 5
[2, 4, 6, 8, 10]适配器只是描述要做什么,一次都不执行。这带来两个好处:可以链式组合而不产生中间集合;编译器能把整条链优化成一个循环。
常用适配器
| 方法 | 作用 |
|---|---|
map | 转换每个元素 |
filter | 保留满足条件的 |
filter_map | 转换 + 过滤 Option |
flat_map | 转换并展平 |
take / skip | 取前 n 个 / 跳过前 n 个 |
take_while | 取到条件不成立为止 |
chain | 拼接两个迭代器 |
zip | 配对 |
enumerate | 加上下标 |
rev | 反转 |
peekable | 可预读下一个 |
Rust
fn main() {
let v = vec![1, 2, 3, 4, 5, 6];
let r: Vec<i32> = v.iter()
.filter(|&&x| x % 2 == 0)
.map(|&x| x * x)
.collect();
println!("{:?}", r);
for (i, x) in v.iter().enumerate().take(3) {
print!("{}:{} ", i, x);
}
println!();
}Output
[4, 16, 36]
0:1 1:2 2:3 消费者
Rust
fn main() {
let v = vec![1, 2, 3, 4, 5];
println!("{}", v.iter().sum::<i32>());
println!("{}", v.iter().product::<i32>());
println!("{:?}", v.iter().max());
println!("{:?}", v.iter().min());
println!("{}", v.iter().count());
println!("{}", v.iter().any(|&x| x > 4));
println!("{}", v.iter().all(|&x| x > 0));
println!("{:?}", v.iter().find(|&&x| x % 3 == 0));
println!("{:?}", v.iter().position(|&x| x == 3));
let s: String = v.iter().map(|x| x.to_string()).collect::<Vec<_>>().join(",");
println!("{}", s);
}Output
15
120
Some(5)
Some(1)
5
true
true
Some(3)
Some(2)
1,2,3,4,5自定义迭代器
Rust
struct Counter {
count: u32,
max: u32,
}
impl Counter {
fn new(max: u32) -> Self {
Counter { count: 0, max }
}
}
impl Iterator for Counter {
type Item = u32;
fn next(&mut self) -> Option<Self::Item> {
if self.count < self.max {
self.count += 1;
Some(self.count)
} else {
None
}
}
}
fn main() {
let v: Vec<u32> = Counter::new(5).collect();
println!("{:?}", v);
let sum: u32 = Counter::new(4).filter(|x| x % 2 == 0).sum();
println!("{}", sum);
}Output
[1, 2, 3, 4, 5]
6实现 Iterator 只需写 next(),其他几十个方法(map、filter、sum……)都是默认实现,自动就有了。这是 trait 默认方法最漂亮的用法之一。
性能
Rust
// 这两种写法编译后的机器码基本相同
let sum1: i32 = (1..=100).filter(|x| x % 2 == 0).sum();
let mut sum2 = 0;
for x in 1..=100 {
if x % 2 == 0 {
sum2 += x;
}
}「零成本抽象」在这里体现得最直接:迭代器链会被内联、融合成一个循环,没有函数调用开销,也没有中间 Vec。所以不要为了性能放弃迭代器写法。
iter() 借元素(&T),into_iter() 消费集合并给值(T),iter_mut() 给可变引用(&mut T)。for x in v 等价于 into_iter()——这就是它会把 v 移走的原因。