讓我們討論一下循環。
還記得Rust的`for`循環嗎?這是一個例子:
~~~
for x in 0..10 {
println!("{}", x);
}
~~~
現在我們更加了解Rust了,我們可以談談這里的具體細節了。這個范圍(`0..10`)是“迭代器”。我們可以重復調用迭代器的`.next()`方法,然后它會給我們一個數據序列。
就像這樣:
~~~
let mut range = 0..10;
loop {
match range.next() {
Some(x) => {
println!("{}", x);
},
None => { break }
}
}
~~~
我們創建了一個`range`的可變綁定,它是我們的迭代器。我們接著`loop`,它包含一個`match`。`match`用來匹配`range.next()`的結果,它給我們迭代器的下一個值。`next`返回一個`Option`,在這個例子中,它會返回`Some(i32)`如果有值然后返回`None`當我們循環完畢。如果我們得到`Some(i32)`,我們打印它,如果我們得到`None`,我們`break`出循環。
這個代碼例子基本上和我們的`loop`版本一樣。`for`只是`loop/match/break`結構的簡便寫法。
然而,`for`循環并不是唯一使用迭代器的結構。編寫你自己的迭代器涉及到實現`Iterator`特性。然而特性不是本章教程的涉及范圍,不過Rust提供了一系列的有用的迭代器幫助我們完成各種任務。在我們開始講解之前,我們需要看看一個Rust的反面模式。這就是如此使用范圍。
是的,我們剛剛談論到范圍是多么的酷。不過范圍也是非常原始的。例如,如果你想迭代一個向量的內容,你可能嘗試這么寫:
~~~
let nums = vec![1, 2, 3];
for i in 0..nums.len() {
println!("{}", nums[i]);
}
~~~
這嚴格的說比使用現成的迭代器還要糟。你可以直接在向量上迭代。所以這么寫:
~~~
let nums = vec![1, 2, 3];
for num in &nums {
println!("{}", num);
}
~~~
這么寫有兩個原因。第一,它更明確的表明了我們的意圖。我們迭代整個向量,而不是先迭代向量的索引,再按索引迭代向量。第二,這個版本也更有效率:第一個版本會進行額外的邊界檢查因為它使用了索引,`nums[i]`。因為我們利用迭代器獲取每個向量元素的引用,第二個例子中并沒有邊界檢查。這在迭代器中非常常見:我們可以忽略不必要的邊界檢查,不過仍然知道我們是安全的。
這里還有一個細節不是100%清楚的就是`println!`是如何工作的。`num`是`&i32`類型。也就是說,它是一個`i32`的引用,并不是`i32`本身。`println!`為我們處理了非關聯化,所以我們看不到。下面的代碼也能工作:
~~~
let nums = vec![1, 2, 3];
for num in &nums {
println!("{}", *num);
}
~~~
現在我們顯式的解引用了`num`。為什么`&nums`會給我們一個引用呢?首先,因為我們顯式的使用了`&`。再次,如果它給我們數據,我們就是它的所有者了,這會涉及到生成數據的拷貝然后返回給我們拷貝。通過引用,我們只是借用了一個數據的引用,所以僅僅是傳遞了一個引用,并不涉及數據的移動。
那么,現在我們已經明確了范圍經產不是我們需要的,讓我們來討論下你需要什么。
這里涉及到大體上相關的3類事物:迭代器,_迭代適配器_(_iterator adapters_)和_消費者_(_consumers_)。下面是一些定義:
* _迭代器_給你一個值的序列
* _迭代適配器_操作迭代器,產生一個不同輸出序列的新迭代器
* _消費者_操作迭代器,產生最終值的集合
讓我們先看看消費者,因為我們已經見過范圍這個迭代器了。
## 消費者
_消費者_操作一個迭代器,返回一些值或者幾種類型的值。最常見的消費者是`collect()`。這個代碼還不能編譯,不過它表明了我們的意圖:
~~~
let one_to_one_hundred = (1..101).collect();
~~~
如你所見,我們在迭代器上調用了`collect()`。`collect()`從迭代器中取得盡可能多的值,然后返回結果的集合。那么為什么這不能編譯呢?因為Rust不能確定你想收集什么類型的值,所以你需要讓它知道。下面是一個可以編譯的版本:
~~~
let one_to_one_hundred = (1..101).collect::<Vec<i32>>();
~~~
如果你還記得,`::<>`語法允許我們給出一個類型提示,所以我們可以告訴編譯器我們需要一個整形的向量。但是你并不總是需要提供完整的類型。使用`_`可以讓你提供一個部分的提示:
~~~
let one_to_one_hundred = (1..101).collect::<Vec<_>>();
~~~
這是指“請把值收集到`Vec`,不過自行推斷`T`類型”。為此`_`有事被稱為“類型占位符”。
`collect()`是最常見的消費者,不過這還有其它的消費者。`find()`就是一個:
~~~
let greater_than_forty_two = (0..100)
.find(|x| *x > 42);
match greater_than_forty_two {
Some(_) => println!("We got some numbers!"),
None => println!("No numbers found :("),
}
~~~
`find`接收一個閉包,然后處理迭代器中每個元素的引用。這個閉包返回`true`如果這個元素是我們要找的,返回`false`如果不是。因為我們可能不能找到任何元素,所以`find`返回`Option`而不是元素本身。
另一個重要的消費者是`fold`。他看起來像這樣:
~~~
let sum = (1..4).fold(0, |sum, x| sum + x);
~~~
`fold()`看起來像這樣:`fold(base, |accumulator, element| ...)`。它需要兩個參數:第一個參數叫做_基數_(_base_)。第二個是一個閉包,它自己也需要兩個參數:第一個叫做_累計數_(_accumulator_),第二個叫_元素_(_element_)。每次迭代,這個閉包都會被調用,返回值是下一次迭代的累計數。在我們的第一次迭代,基數是累計數。
好吧,這有點混亂。讓我們檢查一下這個迭代器中所有這些值:
| 基數 | 累計數 | 元素 | 閉包結果 |
| --- | --- | --- | --- |
| 0 | 0 | 1 | 1 |
| 0 | 1 | 2 | 3 |
| 0 | 3 | 3 | 6 |
我們可以使用這些參數調用`fold()`:
~~~
.fold(0, |sum, x| sum + x);
~~~
那么,`0`是我們的基數,`sum`是累計數,`x`是元素。在第一次迭代,我們設置`sum`為`0`,然后`x`是`nums`的第一個元素,`1`。我們接著把`sum`和`x`相加,得到`0 + 1 = 1`。在我們第二次迭代,`sum`成為我們的累計值,元素是數組的第二個值,`2`,`1 + 2 = 3`,然后它就是最后一次迭代的累計數。在這次迭代中,`x`是最后的元素,`3`,那么`3 + 3 = 6`,就是我們和的最終值。`1 + 2 + 3 = 6`,這就是我們的結果。
(口哨)。最開始你見到`fold`的時候可能覺得有點奇怪,不過一旦你習慣了它,你就會在到處都用它。任何時候你有一個列表,然后你需要一個單一的結果,`fold`就是合適的。
消費者很重要還因為另一個我們沒有討論到的迭代器的屬性:惰性。讓我們更多的討論一下迭代器,你就知道為什么消費者重要了。
## 迭代器
正如我們之前說的,迭代器是一個我們可以重復調用它的`.next()`方法,然后它會給我們一個數據序列的結構。因為你需要調用函數,這意味著迭代器是_懶惰_(_lazy?_)的并且不需要預先生成所有的值。例如,下面的代碼并沒有真正的生成`1-100`這些數,而是創建了一個值來代表這個序列:
~~~
let nums = 1..100;
~~~
因為我們沒有用范圍做任何事,它并生成序列。讓我們加上消費者:
~~~
let nums = (1..100).collect::<Vec<i32>>();
~~~
現在,`collect()`會要求范圍生成一些值,接著它會開始產生序列。
范圍是你會見到的兩個基本迭代器之一。另一個是`iter()`。`iter()`可以把一個向量轉換為一個簡單的按順序給出每個值的迭代器:
~~~
let nums = [1, 2, 3];
for num in nums.iter() {
println!("{}", num);
}
~~~
這兩個基本迭代器應該能勝任你的工作。這還有一些高級迭代器,包括一個是無限的。像`count`:
~~~
std::iter::count(1, 5);
~~~
這個迭代器從1開始計數,每次加5.它每次都會給你一個新值,直到永遠(好吧,從技術上講直到它循環到`i32`所能代表的最大值)。不過因為它是懶惰的,這沒有問題!你可能不會想在它之上使用`collect()`。
足夠關于迭代器的知識了。迭代適配器是關于迭代器最后一個要介紹的內容了。讓我們開始吧!
## 迭代適配器(Iterator adapters)
_迭代適配器_(_Iterator adapters_)獲取一個迭代器然后按某種方法修改它,并產生一個新的迭代器。最簡單的是一個是`map`:
~~~
(1..100).map(|x| x + 1);
~~~
在其他迭代器上調用`map`,然后產生一個新的迭代器,它的每個元素引用被調用了作為參數的閉包。所以它會給我們`2-100`這些數字。好吧,看起來是這樣。如果你編譯這個例子,你會得到一個警告:
~~~
warning: unused result which must be used: iterator adaptors are lazy and
do nothing unless consumed, #[warn(unused_must_use)] on by default
(1..100).map(|x| x + 1);
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~~~
又是惰性!那個閉包永遠也不會執行。這個例子也不會打印任何數字:
~~~
(1..100).map(|x| println!("{}", x));
~~~
如果你嘗試在一個迭代器上執行帶有副作用的閉包,不如直接使用`for`。
這里有大量有趣的迭代適配器。`take(n)`會返回一個源迭代器下`n`個元素的新迭代器,注意這對源迭代器沒有副作用。讓我們試試我們之前的無限迭代器,`count()`:
~~~
for i in std::iter::count(1, 5).take(5) {
println!("{}", i);
}
~~~
這會打印:
~~~
1
6
11
16
21
~~~
`filter()`是一個帶有一個閉包參數的適配器。這個閉包返回`true`或`false`。`filter()`返回的新迭代器只包含閉包返回`true`的元素:
~~~
for i in (1..100).filter(|&x| x % 2 == 0) {
println!("{}", i);
}
~~~
這會打印出1到100之間所有的偶數。(注意因為`filter`并不消費它迭代的元素,它傳遞每個元素的引用,所以過濾器使用`&x`來獲取其中的整形數據。)
你可以鏈式的調用所有三種結構:以一個迭代器開始,適配幾次,然后處理結果。看看下面的:
~~~
(1..1000)
.filter(|&x| x % 2 == 0)
.filter(|&x| x % 3 == 0)
.take(5)
.collect::<Vec<i32>>();
~~~
這會給你一個包含`6`,`12`,`18`,`24`和`30`的向量。
這只是一個迭代器,迭代適配器和消費者如何幫助你的小嘗試。這里有很多非常實用的迭代器,當然你也可以編寫你自己的迭代器。迭代器提供了一個安全,高效的處理所有類型列表的方法。最開始它們顯得比較不尋常,不過如果你玩轉了它們,你就會上癮的。關于不同迭代器和消費者的列表,查看[迭代器模塊文檔](http://doc.rust-lang.org/std/iter/)。
- 前言
- 1.介紹
- 2.準備
- 2.1.安裝Rust
- 2.2.Hello, world!
- 2.3.Hello, Cargo!
- 3.學習Rust
- 3.1.猜猜看
- 3.2.哲學家就餐問題
- 3.3.其它語言中的Rust
- 4.高效Rust
- 4.1.棧和堆
- 4.2.測試
- 4.3.條件編譯
- 4.4.文檔
- 4.5.迭代器
- 4.6.并發
- 4.7.錯誤處理
- 4.8.外部語言接口
- 4.9.Borrow 和 AsRef
- 4.10.發布途徑
- 5.語法和語義
- 5.1.變量綁定
- 5.2.函數
- 5.3.原生類型
- 5.4.注釋
- 5.5.If語句
- 5.6.for循環
- 5.7.while循環
- 5.8.所有權
- 5.9.引用和借用
- 5.10.生命周期
- 5.11.可變性
- 5.12.結構體
- 5.13.枚舉
- 5.14.匹配
- 5.15.模式
- 5.16.方法語法
- 5.17.Vectors
- 5.18.字符串
- 5.19.泛型
- 5.20.Traits
- 5.21.Drop
- 5.22.if let
- 5.23.trait對象
- 5.24.閉包
- 5.25.通用函數調用語法
- 5.26.包裝箱和模塊
- 5.27.`const`和`static`
- 5.28.屬性
- 5.29.`type`別名
- 5.30.類型轉換
- 5.31.關聯類型
- 5.32.不定長類型
- 5.33.運算符和重載
- 5.34.`Deref`強制多態
- 5.35.宏
- 5.36.裸指針
- 6.Rust開發版
- 6.1.編譯器插件
- 6.2.內聯匯編
- 6.3.不使用標準庫
- 6.4.固有功能
- 6.5.語言項
- 6.6.鏈接參數
- 6.7.基準測試
- 6.8.裝箱語法和模式
- 6.9.切片模式
- 6.10.關聯常量
- 7.詞匯表
- 8.學院派研究
- 勘誤