闭包
闭包就像快速函数,不需要名字。有时它们被称为lambda。Closures很容易辨识,因为它们使用||
而不是()
。它们在 Rust 中非常常见,一旦你学会了使用它们,你就会爱不释手。
你可以将一个闭包绑定到一个变量上,然后当你使用它时,它看起来就像一个函数一样。
fn main() { let my_closure = || println!("This is a closure"); my_closure(); }
所以这个闭包什么都不需要:||
,并打印一条信息。This is a closure
.
在||
之间我们可以添加输入变量和类型,就像在()
里面添加函数一样。
fn main() { let my_closure = |x: i32| println!("{}", x); my_closure(5); my_closure(5+5); }
这个打印:
5
10
当闭包变得更复杂时,你可以添加一个代码块。那就可以随心所欲的长。
fn main() { let my_closure = || { let number = 7; let other_number = 10; println!("The two numbers are {} and {}.", number, other_number); // This closure can be as long as we want, just like a function. }; my_closure(); }
但是闭包是特殊的,因为它可以接受闭包之外的变量,即使你只写||
。所以你可以这样做:
fn main() { let number_one = 6; let number_two = 10; let my_closure = || println!("{}", number_one + number_two); my_closure(); }
所以这就打印出了16
。你不需要在 ||
中放入任何东西,因为它可以直接取 number_one
和 number_two
并添加它们。
顺便说一下,这就是closure这个名字的由来,因为它们会取变量并将它们 "包围"在里面。如果你想很正确的说。
- 一个
||
如果不把变量从外面包围起来 那就是一个 "匿名函数". 匿名的意思是 "没有名字"。它的工作原理更像一个普通函数。 ||
从外部包围变量的函数是 "closure"。它把周围的变量 "封闭"起来使用。
但是人们经常会把所有的||
函数都叫做闭包,所以你不用担心名字的问题。我们只对任何带有||
的函数说 "closure",但请记住,它可能意味着一个 "匿名函数"。
为什么要知道这两者的区别呢?因为匿名函数其实和有名字的函数做的机器代码是一样的。它们给人的感觉是 "高层抽象",所以有时候大家会觉得机器代码会很复杂。但是Rust用它生成的机器码和普通函数一样快。
所以我们再来看看闭包能做的一些事情。你也可以这样做:
fn main() { let number_one = 6; let number_two = 10; let my_closure = |x: i32| println!("{}", number_one + number_two + x); my_closure(5); }
这个闭包取number_one
和number_two
。我们还给了它一个新的变量 x
,并说 x
是 5.然后它把这三个加在一起打印 21
。
通常在Rust中,你会在一个方法里面看到闭包,因为里面有一个闭包是非常方便的。我们在上一节的 .map()
和 .for_each()
中看到了闭包。在那一节中,我们写了 |x|
来引入迭代器中的下一个元素,这就是一个闭包。
下面再举一个例子:我们知道,如果unwrap
不起作用,可以用unwrap_or
方法给出一个值。之前我们写的是:let fourth = my_vec.get(3).unwrap_or(&0);
。但是还有一个unwrap_or_else
方法,里面有一个闭包。所以你可以这样做:
fn main() { let my_vec = vec![8, 9, 10]; let fourth = my_vec.get(3).unwrap_or_else(|| { // try to unwrap. If it doesn't work, if my_vec.get(0).is_some() { // see if my_vec has something at index [0] &my_vec[0] // Give the number at index 0 if there is something } else { &0 // otherwise give a &0 } }); println!("{}", fourth); }
当然,闭包也可以很简单。例如,你可以只写let fourth = my_vec.get(3).unwrap_or_else(|| &0);
。你不需要总是因为有一个闭包就使用{}
并写出复杂的代码。只要你把||
放进去,编译器就知道你放了你需要的闭包。
最常用的闭包方法可能是.map()
。我们再来看看它。下面是一种使用方法。
fn main() { let num_vec = vec![2, 4, 6]; let double_vec = num_vec // take num_vec .iter() // iterate over it .map(|number| number * 2) // for each item, multiply by two .collect::<Vec<i32>>(); // then make a new Vec from this println!("{:?}", double_vec); }
另一个很好的例子是在.enumerate()
之后使用.for_each()
。.enumerate()
方法给出一个带有索引号和元素的迭代器。例如:[10, 9, 8]
变成(0, 10), (1, 9), (2, 8)
。这里每个项的类型是(usize, i32)
。所以你可以这样做:
fn main() { let num_vec = vec![10, 9, 8]; num_vec .iter() // iterate over num_vec .enumerate() // get (index, number) .for_each(|(index, number)| println!("Index number {} has number {}", index, number)); // do something for each one }
这个将打印:
Index number 0 has number 10
Index number 1 has number 9
Index number 2 has number 8
在这种情况下,我们用for_each
代替map
。map
是用于对每个元素做一些事情,并将其传递出去,而for_each
是当你看到每个元素时做一些事情。另外,map
不做任何事情,除非你使用collect
这样的方法。
其实,这就是迭代器的有趣之处。如果你尝试map
而不使用collect
这样的方法,编译器会告诉你,它什么也不做。它不会崩溃,但编译器会告诉你,你什么都没做。
fn main() { let num_vec = vec![10, 9, 8]; num_vec .iter() .enumerate() .map(|(index, number)| println!("Index number {} has number {}", index, number)); }
它说:
warning: unused `std::iter::Map` that must be used
--> src\main.rs:4:5
|
4 | / num_vec
5 | | .iter()
6 | | .enumerate()
7 | | .map(|(index, number)| println!("Index number {} has number {}", index, number));
| |_________________________________________________________________________________________^
|
= note: `#[warn(unused_must_use)]` on by default
= note: iterators are lazy and do nothing unless consumed
这是一个警告,所以这不是一个错误:程序运行正常。但是为什么num_vec没有任何作用呢?我们可以看看类型就知道了。
-
let num_vec = vec![10, 9, 8];
现在是一个Vec<i32>
。 -
.iter()
现在是一个Iter<i32>
。所以它是一个迭代器,其元素为i32
。 -
.enumerate()
现在是一个Enumerate<Iter<i32>>
型。所以它是Enumerate
型的Iter
型的i32
。 -
.map()
现在是一个Map<Enumerate<Iter<i32>>>
的类型。所以它是一个类型Map
的类型Enumerate
的类型Iter
的类型i32
。
我们所做的只是做了一个越来越复杂的结构。所以这个Map<Enumerate<Iter<i32>>>
是一个准备好了的结构,但只有当我们告诉它要做什么的时候,它才会去做。Rust这样做是因为它需要保证足够快。它不想这样做:
- 遍历Vec中所有的
i32
- 然后从迭代器中枚举出所有的
i32
- 然后将所有列举的
i32
映射过来
Rust 只想做一次计算,所以它创建结构并等待。然后,如果我们说.collect::<Vec<i32>>()
,它知道该怎么做,并开始移动。这就是iterators are lazy and do nothing unless consumed
的意思。迭代器在你 "消耗"它们(用完它们)之前不会做任何事情。
你甚至可以用.collect()
创建像HashMap
这样复杂的东西,所以它非常强大。下面是一个如何将两个向量放入HashMap
的例子。首先我们把两个向量创建出来,然后我们会对它们使用.into_iter()
来得到一个值的迭代器。然后我们使用.zip()
方法。这个方法将两个迭代器连接在一起,就像拉链一样。最后,我们使用.collect()
来创建HashMap
。
下面是代码。
use std::collections::HashMap; fn main() { let some_numbers = vec![0, 1, 2, 3, 4, 5]; // a Vec<i32> let some_words = vec!["zero", "one", "two", "three", "four", "five"]; // a Vec<&str> let number_word_hashmap = some_numbers .into_iter() // now it is an iter .zip(some_words.into_iter()) // inside .zip() we put in the other iter. Now they are together. .collect::<HashMap<_, _>>(); println!("For key {} we get {}.", 2, number_word_hashmap.get(&2).unwrap()); }
这个将打印:
For key 2 we get two.
你可以看到,我们写了 <HashMap<_, _>>
,因为这足以让 Rust 决定 HashMap<i32, &str>
的类型。如果你想写 .collect::<HashMap<i32, &str>>();
也行,也可以这样写:
use std::collections::HashMap; fn main() { let some_numbers = vec![0, 1, 2, 3, 4, 5]; // a Vec<i32> let some_words = vec!["zero", "one", "two", "three", "four", "five"]; // a Vec<&str> let number_word_hashmap: HashMap<_, _> = some_numbers // Because we tell it the type here... .into_iter() .zip(some_words.into_iter()) .collect(); // we don't have to tell it here }
还有一种方法,就像.enumerate()
的char
。char_indices()
. (Indices的意思是 "索引")。你用它的方法是一样的。假设我们有一个由3位数组成的大字符串。
fn main() { let numbers_together = "140399923481800622623218009598281"; for (index, number) in numbers_together.char_indices() { match (index % 3, number) { (0..=1, number) => print!("{}", number), // just print the number if there is a remainder _ => print!("{}\t", number), // otherwise print the number with a tab space } } }
打印140 399 923 481 800 622 623 218 009 598 281
。
闭包中的|_|
有时你会在一个闭包中看到 |_|
。这意味着这个闭包需要一个参数(比如 x
),但你不想使用它。所以 |_|
意味着 "好吧,这个闭包需要一个参数,但我不会给它一个名字,因为我不关心它"。
下面是一个错误的例子,当你不这样做的时候。
fn main() { let my_vec = vec![8, 9, 10]; println!("{:?}", my_vec.iter().for_each(|| println!("We didn't use the variables at all"))); // ⚠️ }
Rust说
error[E0593]: closure is expected to take 1 argument, but it takes 0 arguments
--> src\main.rs:28:36
|
28 | println!("{:?}", my_vec.iter().for_each(|| println!("We didn't use the variables at all")));
| ^^^^^^^^ -- takes 0 arguments
| |
| expected closure that takes 1 argument
编译器其实给你一些帮助。
help: consider changing the closure to take and ignore the expected argument
|
28 | println!("{:?}", my_vec.iter().for_each(|_| println!("We didn't use the variables at all")));
这是很好的建议。如果你把||
改成|_|
就可以了。
闭包和迭代器的有用方法
一旦你熟悉了闭包,Rust就会成为一种非常有趣的语言。有了闭包,你可以将方法互相链接起来,用很少的代码做很多事情。下面是一些我们还没有见过的闭包和使用闭包的方法。
.filter()
: 这可以让你在迭代器中保留你想保留的元素。让我们过滤一年中的月份。
fn main() { let months = vec!["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]; let filtered_months = months .into_iter() // make an iter .filter(|month| month.len() < 5) // We don't want months more than 5 bytes in length. // We know that each letter is one byte so .len() is fine .filter(|month| month.contains("u")) // Also we only like months with the letter u .collect::<Vec<&str>>(); println!("{:?}", filtered_months); }
这个打印["June", "July"]
。
.filter_map()
. 这个叫做filter_map()
,因为它做了.filter()
和.map()
。闭包必须返回一个 Option<T>
,然后对每个Option
, 如果是 Some
, filter_map()
将取出它的值。所以比如说你.filter_map()
一个vec![Some(2), None, Some(3)]
,它就会返回[2, 3]
。
我们将用一个Company
结构体来写一个例子。每个公司都有一个name
,所以这个字段是String
,但是CEO可能最近已经辞职了。所以ceo
字段是Option<String>
。我们会.filter_map()
过一些公司,只保留CEO名字。
struct Company { name: String, ceo: Option<String>, } impl Company { fn new(name: &str, ceo: &str) -> Self { let ceo = match ceo { "" => None, name => Some(name.to_string()), }; // ceo is decided, so now we return Self Self { name: name.to_string(), ceo, } } fn get_ceo(&self) -> Option<String> { self.ceo.clone() // Just returns a clone of the CEO (struct is not Copy) } } fn main() { let company_vec = vec![ Company::new("Umbrella Corporation", "Unknown"), Company::new("Ovintiv", "Doug Suttles"), Company::new("The Red-Headed League", ""), Company::new("Stark Enterprises", ""), ]; let all_the_ceos = company_vec .into_iter() .filter_map(|company| company.get_ceo()) // filter_map needs Option<T> .collect::<Vec<String>>(); println!("{:?}", all_the_ceos); }
这就打印出了["Unknown", "Doug Suttles"]
。
既然 .filter_map()
需要 Option
,那么 Result
呢?没问题:有一个叫做 .ok()
的方法,可以把 Result
变成 Option
。之所以叫.ok()
,是因为它能发送的只是Ok
的结果(Err
的信息没有了)。你记得Option
是Option<T>
,而Result
是Result<T, E>
,同时有Ok
和Err
的信息。所以当你使用.ok()
时,任何Err
的信息都会丢失,变成None
。
使用 .parse()
是一个很简单的例子,我们尝试解析一些用户输入。.parse()
在这里接受一个&str
,并试图把它变成一个f32
。它返回一个 Result
,但我们使用的是 filter_map()
,所以我们只需抛出错误。Err
的任何内容都会变成None
,并被.filter_map()
过滤掉。
fn main() { let user_input = vec!["8.9", "Nine point nine five", "8.0", "7.6", "eleventy-twelve"]; let actual_numbers = user_input .into_iter() .filter_map(|input| input.parse::<f32>().ok()) .collect::<Vec<f32>>(); println!("{:?}", actual_numbers); }
将打印: [8.9, 8.0, 7.6]
。
与.ok()
相对的是.ok_or()
和ok_or_else()
。这样就把Option
变成了Result
。之所以叫.ok_or()
,是因为Result
给出了一个Ok
或Err
,所以你必须让它知道Err
的值是多少。这是因为None
中的Option
没有任何信息。另外,你现在可以看到,这些方法名称中的else部分意味着它有一个闭包。
我们可以把我们的Option
从Company
结构中取出来,然后这样把它变成一个Result
。对于长期的错误处理,最好是创建自己的错误类型。
但是现在我们只是给它一个错误信息,所以它就变成了Result<String, &str>
。
// Everything before main() is exactly the same struct Company { name: String, ceo: Option<String>, } impl Company { fn new(name: &str, ceo: &str) -> Self { let ceo = match ceo { "" => None, name => Some(name.to_string()), }; Self { name: name.to_string(), ceo, } } fn get_ceo(&self) -> Option<String> { self.ceo.clone() } } fn main() { let company_vec = vec![ Company::new("Umbrella Corporation", "Unknown"), Company::new("Ovintiv", "Doug Suttles"), Company::new("The Red-Headed League", ""), Company::new("Stark Enterprises", ""), ]; let mut results_vec = vec![]; // Pretend we need to gather error results too company_vec .iter() .for_each(|company| results_vec.push(company.get_ceo().ok_or("No CEO found"))); for item in results_vec { println!("{:?}", item); } }
这行是最大的变化:
#![allow(unused)] fn main() { // 🚧 .for_each(|company| results_vec.push(company.get_ceo().ok_or("No CEO found"))); }
它的意思是:"每家公司,用get_ceo()
. 如果你得到了,那就把Ok
里面的数值传给你。如果没有,就在Err
里面传递 "没有找到CEO"。然后把这个推到vec里。"
所以当我们打印results_vec
的时候,就会得到这样的结果。
Ok("Unknown")
Ok("Doug Suttles")
Err("No CEO found")
Err("No CEO found")
所以现在我们有了所有四个条目。现在让我们使用 .ok_or_else()
,这样我们就可以使用一个闭包,并得到一个更好的错误信息。现在我们有空间使用format!
来创建一个String
,并将公司名称放在其中。然后我们返回String
。
// Everything before main() is exactly the same struct Company { name: String, ceo: Option<String>, } impl Company { fn new(name: &str, ceo: &str) -> Self { let ceo = match ceo { "" => None, name => Some(name.to_string()), }; Self { name: name.to_string(), ceo, } } fn get_ceo(&self) -> Option<String> { self.ceo.clone() } } fn main() { let company_vec = vec![ Company::new("Umbrella Corporation", "Unknown"), Company::new("Ovintiv", "Doug Suttles"), Company::new("The Red-Headed League", ""), Company::new("Stark Enterprises", ""), ]; let mut results_vec = vec![]; company_vec.iter().for_each(|company| { results_vec.push(company.get_ceo().ok_or_else(|| { let err_message = format!("No CEO found for {}", company.name); err_message })) }); for item in results_vec { println!("{:?}", item); } }
这样一来,我们就有了。
Ok("Unknown")
Ok("Doug Suttles")
Err("No CEO found for The Red-Headed League")
Err("No CEO found for Stark Enterprises")
.and_then()
是一个有用的方法,它接收一个Option
,然后让你对它的值做一些事情,并把它传递出去。所以它的输入是一个 Option
,输出也是一个 Option
。这有点像一个安全的 "解包,然后做一些事情,然后再包"。
一个简单的例子是,我们使用 .get()
从一个 vec 中得到一个数字,因为它返回一个 Option
。现在我们可以把它传给 and_then()
,如果它是 Some
,我们可以对它做一些数学运算。如果是None
,那么None
就会被传递过去。
fn main() { let new_vec = vec![8, 9, 0]; // just a vec with numbers let number_to_add = 5; // use this in the math later let mut empty_vec = vec![]; // results go in here for index in 0..5 { empty_vec.push( new_vec .get(index) .and_then(|number| Some(number + 1)) .and_then(|number| Some(number + number_to_add)) ); } println!("{:?}", empty_vec); }
这就打印出了[Some(14), Some(15), Some(6), None, None]
。你可以看到None
并没有被过滤掉,只是传递了。
.and()
有点像Option
的bool
。你可以匹配很多个Option
,如果它们都是Some
,那么它会给出最后一个。而如果其中一个是None
,那么就会给出None
。
首先这里有一个bool
的例子来帮助想象。你可以看到,如果你用的是&&
(和),哪怕是一个false
,也会让一切false
。
fn main() { let one = true; let two = false; let three = true; let four = true; println!("{}", one && three); // prints true println!("{}", one && two && three && four); // prints false }
现在这里的.and()
也是一样的。想象一下,我们做了五次操作,并把结果放在一个Vec<Option<&str>>中。如果我们得到一个值,我们就把Some("success!")
推到Vec中。然后我们再做两次这样的操作。之后我们用.and()
每次只显示得到Some
的索引。
fn main() { let first_try = vec![Some("success!"), None, Some("success!"), Some("success!"), None]; let second_try = vec![None, Some("success!"), Some("success!"), Some("success!"), Some("success!")]; let third_try = vec![Some("success!"), Some("success!"), Some("success!"), Some("success!"), None]; for i in 0..first_try.len() { println!("{:?}", first_try[i].and(second_try[i]).and(third_try[i])); } }
这个打印:
None
None
Some("success!")
Some("success!")
None
第一个(索引0)是None
,因为在second_try
中有一个None
为索引0。第二个是None
,因为在first_try
中有一个None
。其次是Some("success!")
,因为first_try
、second try
、third_try
中没有None
。
.any()
和.all()
在迭代器中非常容易使用。它们根据你的输入返回一个bool
。在这个例子中,我们做了一个非常大的vec(大约20000个元素),包含了从'a'
到'働'
的所有字符。然后我们创建一个函数来检查是否有字符在其中。
接下来我们创建一个更小的vec,问它是否都是字母(用.is_alphabetic()
方法)。然后我们问它是不是所有的字符都小于韩文字符'행'
。
还要注意放一个参照物,因为.iter()
给了一个参照物,你需要一个&
和另一个&
进行比较。
fn in_char_vec(char_vec: &Vec<char>, check: char) { println!("Is {} inside? {}", check, char_vec.iter().any(|&char| char == check)); } fn main() { let char_vec = ('a'..'働').collect::<Vec<char>>(); in_char_vec(&char_vec, 'i'); in_char_vec(&char_vec, '뷁'); in_char_vec(&char_vec, '鑿'); let smaller_vec = ('A'..'z').collect::<Vec<char>>(); println!("All alphabetic? {}", smaller_vec.iter().all(|&x| x.is_alphabetic())); println!("All less than the character 행? {}", smaller_vec.iter().all(|&x| x < '행')); }
这个打印:
Is i inside? true
Is 뷁 inside? false
Is 鑿 inside? false
All alphabetic? false
All less than the character 행? true
顺便说一下,.any()
只检查到一个匹配的元素,然后就停止了。如果它已经找到了一个匹配项,它不会检查所有的元素。如果您要在 Vec
上使用 .any()
,最好把可能匹配的元素推到前面。或者你可以在 .iter()
之后使用 .rev()
来反向迭代。这里有一个这样的vec。
fn main() { let mut big_vec = vec![6; 1000]; big_vec.push(5); }
所以这个Vec
有1000个6
,后面还有一个5
。我们假设我们要用.any()
来看看它是否包含5。首先让我们确定.rev()
是有效的。记住,一个Iterator
总是有.next()
,让你每次都检查它的工作。
fn main() { let mut big_vec = vec![6; 1000]; big_vec.push(5); let mut iterator = big_vec.iter().rev(); println!("{:?}", iterator.next()); println!("{:?}", iterator.next()); }
它的打印。
Some(5)
Some(6)
我们是对的:有一个Some(5)
,然后1000个Some(6)
开始。所以我们可以这样写。
fn main() { let mut big_vec = vec![6; 1000]; big_vec.push(5); println!("{:?}", big_vec.iter().rev().any(|&number| number == 5)); }
而且因为是.rev()
,所以它只调用.next()
一次就停止了。如果我们不用.rev()
,那么它将调用.next()
1001次才停止。这段代码显示了它。
fn main() { let mut big_vec = vec![6; 1000]; big_vec.push(5); let mut counter = 0; // Start counting let mut big_iter = big_vec.into_iter(); // Make it an Iterator loop { counter +=1; if big_iter.next() == Some(5) { // Keep calling .next() until we get Some(5) break; } } println!("Final counter is: {}", counter); }
这将打印出 Final counter is: 1001
,所以我们知道它必须调用 .next()
1001 次才能找到 5。
.find()
告诉你一个迭代器是否有东西,而 .position()
告诉你它在哪里。.find()
与.any()
不同,因为它返回一个Option
,里面有值(或None
)。同时,.position()
也是一个带有位置号的Option
,或None
。换句话说
.find()
: "我尽量帮你拿".position()
:"我帮你找找看在哪里"
下面是一个简单的例子。
fn main() { let num_vec = vec![10, 20, 30, 40, 50, 60, 70, 80, 90, 100]; println!("{:?}", num_vec.iter().find(|&number| number % 3 == 0)); // find takes a reference, so we give it &number println!("{:?}", num_vec.iter().find(|&number| number * 2 == 30)); println!("{:?}", num_vec.iter().position(|&number| number % 3 == 0)); println!("{:?}", num_vec.iter().position(|&number| number * 2 == 30)); }
这个打印:
Some(30) // This is the number itself
None // No number inside times 2 == 30
Some(2) // This is the position
None
使用 .cycle()
你可以创建一个永远循环的迭代器。这种类型的迭代器与 .zip()
很好地结合在一起,可以创建新的东西,就像这个例子,它创建了一个 Vec<(i32, &str)>
。
fn main() { let even_odd = vec!["even", "odd"]; let even_odd_vec = (0..6) .zip(even_odd.into_iter().cycle()) .collect::<Vec<(i32, &str)>>(); println!("{:?}", even_odd_vec); }
所以,即使.cycle()
可能永远不会结束,但当把它们压缩在一起时,另一个迭代器只运行了6次。
也就是说,.cycle()
所做的迭代器不会再被.next()
调用,所以六次之后就完成了。输出的结果是
[(0, "even"), (1, "odd"), (2, "even"), (3, "odd"), (4, "even"), (5, "odd")]
类似的事情也可以用一个没有结尾的范围来完成。如果你写0..
,那么你就创建了一个永不停止的范围。你可以很容易地使用这个方法。
fn main() { let ten_chars = ('a'..).take(10).collect::<Vec<char>>(); let skip_then_ten_chars = ('a'..).skip(1300).take(10).collect::<Vec<char>>(); println!("{:?}", ten_chars); println!("{:?}", skip_then_ten_chars); }
两者都是打印十个字符,但第二个跳过1300位,打印的是亚美尼亚语的十个字母。
['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j']
['յ', 'ն', 'շ', 'ո', 'չ', 'պ', 'ջ', 'ռ', 'ս', 'վ']
另一种流行的方法叫做.fold()
。这个方法经常用于将迭代器中的元素加在一起,但你也可以做更多的事情。它与.for_each()
有些类似。在 .fold()
中,你首先添加一个起始值 (如果你是把元素加在一起,那么就是 0),然后是一个逗号,然后是闭包。结尾给你两个元素:到目前为止的总数,和下一个元素。首先这里有一个简单的例子,显示.fold()
将元素加在一起。
fn main() { let some_numbers = vec![9, 6, 9, 10, 11]; println!("{}", some_numbers .iter() .fold(0, |total_so_far, next_number| total_so_far + next_number) ); }
所以,在第1步中,它从0开始,再加上下一个数字:9。
- 第1步,从0开始,加上下一个数字9
- 然后把9加上6: 15。
- 然后把15加上9: 24。
- 然后取24,再加上10: 34。
- 最后取34,再加上11: 45。所以它的打印结果是
45
.
但是你不需要只用它来添加东西。下面是一个例子,我们在每一个字符上加一个'-',就会变成String
。
fn main() { let a_string = "I don't have any dashes in me."; println!( "{}", a_string .chars() // Now it's an iterator .fold("-".to_string(), |mut string_so_far, next_char| { // Start with a String "-". Bring it in as mutable each time along with the next char string_so_far.push(next_char); // Push the char on, then '-' string_so_far.push('-'); string_so_far} // Don't forget to pass it on to the next loop )); }
这个打印:
-I- -d-o-n-'-t- -h-a-v-e- -a-n-y- -d-a-s-h-e-s- -i-n- -m-e-.-
还有很多其他方便的方法,比如
.take_while()
,只要得到true
,就会带入一个迭代器(例如take while x > 5
).cloned()
,它在迭代器内做了一个克隆。这将一个引用变成了一个值。.by_ref()
,它使迭代器取一个引用。这很好的保证了你使用Vec
或类似的方法来创建迭代器后可以使用它。- 许多其他的
_while
方法:.skip_while()
、.map_while()
等等。 .sum()
:就是把所有的东西加在一起。
.chunks()
和.windows()
是将矢量切割成你想要的尺寸的两种方法。你把你想要的尺寸放在括号里。比如说你有一个有10个元素的矢量,你想要一个3的尺寸,它的工作原理是这样的。
-
.chunks()
会给你4个切片: [0, 1, 2], 然后是[3, 4, 5], 然后是[6, 7, 8], 最后是[9]. 所以它会尝试用三个元素创建一个切片,但如果它没有三个元素,那么它就不会崩溃。它只会给你剩下的东西。 -
.windows()
会先给你一个[0, 1, 2]的切片。然后它将移过一片,给你[1, 2, 3]。它将一直这样做,直到最后到达3的最后一片,然后停止。
所以让我们在一个简单的数字向量上使用它们。它看起来像这样:
fn main() { let num_vec = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 0]; for chunk in num_vec.chunks(3) { println!("{:?}", chunk); } println!(); for window in num_vec.windows(3) { println!("{:?}", window); } }
这个打印:
[1, 2, 3]
[4, 5, 6]
[7, 8, 9]
[0]
[1, 2, 3]
[2, 3, 4]
[3, 4, 5]
[4, 5, 6]
[5, 6, 7]
[6, 7, 8]
[7, 8, 9]
[8, 9, 0]
顺便说一下,如果你什么都不给它,.chunks()
会崩溃。你可以为一个只有一项的向量写.chunks(1000)
,但你不能为任何长度为0的东西写.chunks()
。 如果你点击[src],你可以在函数中看到这一点,因为它说assert!(chunk_size != 0);
。
.match_indices()
让你把 String
或 &str
里面所有符合你的输入的东西都提取出来,并给你索引。它与 .enumerate()
类似,因为它返回一个包含两个元素的元组。
fn main() { let rules = "Rule number 1: No fighting. Rule number 2: Go to bed at 8 pm. Rule number 3: Wake up at 6 am."; let rule_locations = rules.match_indices("Rule").collect::<Vec<(_, _)>>(); // This is Vec<usize, &str> but we just tell Rust to do it println!("{:?}", rule_locations); }
这个打印:
[(0, "Rule"), (28, "Rule"), (62, "Rule")]
.peekable()
让你创建一个迭代器,在那里你可以看到 (窥视) 下一个元素。它就像调用 .next()
(它给出了一个 Option
),除了迭代器不会移动,所以你可以随意使用它。实际上,你可以把peekable看成是 "可停止"的,因为你可以想停多久就停多久。下面是一个例子,我们对每个元素都使用.peek()
三次。我们可以永远使用.peek()
,直到我们使用.next()
移动到下一个元素。
fn main() { let just_numbers = vec![1, 5, 100]; let mut number_iter = just_numbers.iter().peekable(); // This actually creates a type of iterator called Peekable for _ in 0..3 { println!("I love the number {}", number_iter.peek().unwrap()); println!("I really love the number {}", number_iter.peek().unwrap()); println!("{} is such a nice number", number_iter.peek().unwrap()); number_iter.next(); } }
这个打印:
I love the number 1
I really love the number 1
1 is such a nice number
I love the number 5
I really love the number 5
5 is such a nice number
I love the number 100
I really love the number 100
100 is such a nice number
下面是另一个例子,我们使用.peek()
对一个元素进行匹配。使用完后,我们调用.next()
。
fn main() { let locations = vec![ ("Nevis", 25), ("Taber", 8428), ("Markerville", 45), ("Cardston", 3585), ]; let mut location_iter = locations.iter().peekable(); while location_iter.peek().is_some() { match location_iter.peek() { Some((name, number)) if *number < 100 => { // .peek() gives us a reference so we need * println!("Found a hamlet: {} with {} people", name, number) } Some((name, number)) => println!("Found a town: {} with {} people", name, number), None => break, } location_iter.next(); } }
这个打印:
Found a hamlet: Nevis with 25 people
Found a town: Taber with 8428 people
Found a hamlet: Markerville with 45 people
Found a town: Cardston with 3585 people
最后,这里有一个例子,我们也使用.match_indices()
。在这个例子中,我们根据&str
中的空格数,将名字放入struct
中。
#[derive(Debug)] struct Names { one_word: Vec<String>, two_words: Vec<String>, three_words: Vec<String>, } fn main() { let vec_of_names = vec![ "Caesar", "Frodo Baggins", "Bilbo Baggins", "Jean-Luc Picard", "Data", "Rand Al'Thor", "Paul Atreides", "Barack Hussein Obama", "Bill Jefferson Clinton", ]; let mut iter_of_names = vec_of_names.iter().peekable(); let mut all_names = Names { // start an empty Names struct one_word: vec![], two_words: vec![], three_words: vec![], }; while iter_of_names.peek().is_some() { let next_item = iter_of_names.next().unwrap(); // We can use .unwrap() because we know it is Some match next_item.match_indices(' ').collect::<Vec<_>>().len() { // Create a quick vec using .match_indices and check the length 0 => all_names.one_word.push(next_item.to_string()), 1 => all_names.two_words.push(next_item.to_string()), _ => all_names.three_words.push(next_item.to_string()), } } println!("{:?}", all_names); }
这将打印:
Names { one_word: ["Caesar", "Data"], two_words: ["Frodo Baggins", "Bilbo Baggins", "Jean-Luc Picard", "Rand Al\'Thor", "Paul Atreides"], three_words:
["Barack Hussein Obama", "Bill Jefferson Clinton"] }