本文是对Programming Rust 第15章的总结,部分代码与图片来自此书
一个简单的例子
1
2
3
4
5
6
7
8
9
| //normal style
let mut sum = 0;
for i in 0..=10 {
sum += i;
}
println!("{}", sum);
//iterator style
let sum = (0..=10).fold(0, |sum, i| sum+i);
println!("{}", sum);
|
以上两种求和的实现是等价, 基于iterator的实现风格对于函数式编程是很常见的, rust也提倡这种方式的写法,不同的是,rust编译器在编译的时候会对这种表达方式进行优化,达到一种zero cost abstraction的效果, 比如以上的求和在编译器优化后可能汇编代码使用11*10/2这种数学公式来执行。这个意思是不用太担心iterator风格的性能问题, rust编译器会进行必要的优化,使得我们使用的时候表达能力与性能兼顾
IntoInterator和Iterator
在rust中iterator最基本的两个trait是IntoInterator和Iterator,他们的定义如下
1
2
3
4
5
6
7
8
9
10
11
| trait Iterator {
type Item;
fn next(&mut self) -> Option<Self::Item>;
... // many default methods
}
trait IntoIterator where Self::IntoIter:Iterator<Item=Self::Item> {
type Item;
type IntoIter: Iterator;
fn into_iter(self) -> Self::IntoIter;
}
|
IntoIterator用来生成Iterator, 实现Iterator可以进行相应的操作, Iteratortrait包含了很多函数, 但是需要实现的只有一个next.
值得一提的是IntoIterator的into_iter使用传值的方式,也就表明了这个方法会将原始类型给消费掉, 进行Copy或者Move, 比如
1
2
3
4
| let v = vec![1,2,3];
//into_iter() move the vecotr v
let iter = v.into_iter();
println!("{}", v[0]);
|
以上的代码不能编译通过, 因为v已经被Move了, 你可以使用引用来避免这个问题
1
2
3
4
5
| let v = vec![1,2,3];
//以下两种方式等价
let iter = (&v).into_iter();
let iter2 = v.iter();
println!("{}", v[0]);
|
由于这个使用方式很常见, 所以Vec直接提供了方法Vec<T>::iter(), 他会返回一个生成&T类型的Iterator
rust中的for loop其实是使用Iterator来实现的,以下的代码是等价的
1
2
3
4
5
6
7
8
9
10
| let v = vec![1,2,3,4];
for i in &v {
println!("{}", i);
}
//translate to this by compiler
let mut iter = v.iter();
while let Some(i) = iter.next() {
println!("{}", i);
}
|
很多时候可以使用IntoIterator作为泛型的边界来使用,比如
1
2
3
4
5
6
7
8
9
| use std::fmt::Debug;
fn dump<T, U>(t: T)
where T: IntoIterator<Item=U>,
U: Debug
{
for u in t {
println!("{:?}", u);
}
}
|
在这个例子里面,表示T必须是一种可以产生Iterator, 这个Iterator<U>产出的类型U必须是实现了Debug的
drain方法
很多collection提供了drain方法, 它通过collection的可变引用返回一个Iterator, 当这个Iteratro drop之后, 这些被包含的元素就从colleciton当中移除了
1
2
3
4
5
| let mut v = vec![1,2,3,4];
{
let drain_iter = v.drain(2..4);
}
assert_eq!(&v, &vec![1,2]);
|
标准库应用
Iterator在标准库中有广泛的应用, 不止在collection中, 还在很多其他方面, 比如io当中, 使用Iterator来处理io流, 尤其在异步io中。
另外对Iterator风格代码的使用, 对于非函数式编程习惯的人员来说有一定的门槛,需要多练习才能适应,下面将重点介绍以下Iterator适配器
Iterator适配器
rust的Iterator适配器非常灵活,下面将逐个介绍
map and filter
这两个应该很好理解
统一处理Vec中的元素
1
2
3
4
5
6
| let text = " ponies \n giraffes\niguanas
\nsquid".to_string();
let v: Vec<&str> = text.lines()
.map(str::trim) ////map的FnMut类型为FnMut(I::Item) -> B, Item由上一个Iterator决定, B由这个FnMut决定
.collect();
assert_eq!(v, ["ponies", "giraffes", "iguanas", "squid"]);
|
过滤Vec中的元素
1
2
3
4
5
6
7
| let text = " ponies \n giraffes\niguanas
\nsquid".to_string();
let v: Vec<&str> = text.lines()
.map(str::trim)
.filter(|s| *s != "iguanas") //FnMut(&Self::Item) -> bool, Item由上一个Iterator决定
.collect();
assert_eq!(v, ["ponies", "giraffes", "squid"]);
|
需要注意的是
- rust中adaptor是
lazy的,也就是说不管chain了多少个adaptor,这些操作只有被消费的时候才执行,具体就是只有adaptor调用了collect的时候, 操作才被真正执行 - adaptor是
zero cost abstraction, 这意味着rust编译器会将这些chain内联到最后消费的地方, 比如执行的时候就像是一个for循环语句一样,这样做可以明显的提高执行效率
filter_map and flat_map
filter_map有点像map和filter的组合体, 它要么转换一个值,要么丢弃一个值,使用filter_map有时候比使用组合方式更加方便和优雅, 比如只有在你转换一个值的时候才能决定是否要丢弃一个值
1
2
3
4
5
6
7
8
|
let text = "1\nfrond .25 289\n3.1415 estuary\n";
for number in text
.split_whitespace()
.filter_map(|w| f64::from_str(w).ok())
{
println!("{:4.2}", number.sqrt());
}
|
在上个例子中, 你只有做数字转换的时候才知道要不要保留这个值
flat_map就是在每个FnMut中制造出一个Iterator, 然后再把这些Iterator合并起来, 当然这些都是语义层面的, 真正执行的时候, 编译器会做相应的优化, 可能并不会真的做Iterator合并
1
2
3
4
5
6
7
8
9
10
11
12
13
| let mut major_cities = HashMap::new();
major_cities.insert("Japan", vec!["Tokyo", "Kyoto"]);
major_cities.insert("The United States", vec!["Portland",
"Nashville"]);
major_cities.insert("Brazil", vec!["São Paulo", "Brasília"]);
major_cities.insert("Kenya", vec!["Nairobi", "Mombasa"]);
major_cities.insert("The Netherlands", vec!["Amsterdam",
"Utrecht"]);
let countries = ["Japan", "Brazil", "Kenya"];
for &city in countries.iter().flat_map(|country|
&major_cities[country]) {
println!("{}", city);
}
|
flatten
flatten就是值将Iterator中的每个元素也当成Iterator,然后取第二级中的元素作为输出, flatten通常是值将2层级变为1层级的操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| let mut parks = BTreeMap::new();
parks.insert("Portland", vec!["Mt. Tabor Park", "Forest Park"]);
parks.insert("Kyoto", vec!["Tadasu-no-Mori Forest", "Maruyama
Koen"]);
parks.insert("Nashville", vec!["Percy Warner Park", "Dragon
Park"]);
// Build a vector of all parks. `values` gives us an iterator producing
// vectors, and then `flatten` produces each vector's elements in turn.
let all_parks: Vec<_> =
parks.values().flatten().cloned().collect();
assert_eq!(all_parks,
vec!["Tadasu-no-Mori Forest", "Maruyama Koen", "Percy
Warner Park",
"Dragon Park", "Mt. Tabor Park", "Forest Park"])
|
上面的示例将Vec元素打平成&str作为结果, 3个Vec变成了6个&str
flatten与Option配合使用将会有很妙的效果
1
2
3
4
5
| assert_eq!(vec![None, Some("day"), None, Some("one")]
.into_iter()
.flatten()
.collect::<Vec<_>>(),
vec!["day", "one"]);
|
上面的示例将Vec<Option>变成了Vec<&str>, 这是因为Option本生也实现了IntoIterator 返回了非None的值,同样的方式也适用于Result
有的时候flatten与map的组合其实就等于flat_map, 这种情况下使用flat_map会显得更方便与直接
其他
其他还有很多常用的适配器,比如take, skip, peekable, fuse, chain 等等,暂且不做讨论