Iterator(迭代器)是一個比較常見的概念,大部分語言都有。大部分語言實現迭代器的邏輯並沒有特別的。
看完了有關內容,作者的意思是:rust的迭代器和匿名函式一樣,都是為了提供時下流行的函數語言程式設計。
此二者為rust實現零成本抽象提供了不少的貢獻。
本部分概念比較多,如果是程式設計初學者可能會有點吃力,如果是老手還是比較容易理解的。
一、迭代器基本概念
問題:
- 迭代器是什麼
- 如何建立
- 迭代器佑什麼用
1.1、迭代器是什麼
迭代器:能夠逐個獲取集合元素(以集合元素居多,實際可能不一定是集合)並指向下一個元素的物件(一段程式碼)
迭代器是一種簡單抽象,無論其程式碼如何實現,但本質上差不多:一個next方法(叫什麼無所謂),一個指示器(也許更多,但大概就是這個意思)
1.2、如何建立
pub trait Iterator { type Item; fn next(&mut self) -> Option<Self::Item>; // 此處省略了方法的預設實現 }
一個物件(結構或者什麼的)只要實現這個特質就可以建立迭代器。
例如以下為特定的列舉建立一個迭代器:
/** * */ struct Books{ names:String, author:String, content:String } impl Books { fn iter(&mut self) -> BooksIterator { BooksIterator{ current_index:0, strs:vec![self.names.clone(), self.author.clone(),self.content.clone()] } } } /** * 關鍵之一:定義一個臨時結構,以便迭代器能夠訪問結構中的欄位,並進行查詢 */ struct BooksIterator{ current_index:usize, strs:Vec<String> } /** * 實現迭代器特質:Iterator */ impl Iterator for BooksIterator { type Item = String; fn next(&mut self) -> Option<String> { if self.current_index < 3 { let item = self.strs[self.current_index].clone(); self.current_index += 1; Some(item) } else { None } } } fn main() { let mut b = Books{ names:String::from("星星之火可以燎原"), author:String::from("毛->澤東"), content:String::from("新年已經到來幾天了,你的信我還沒有回答。 一則因為有些事情忙著,二則也因為我到底寫點什麼給你呢? 有什麼好一點的東西可以貢獻給你呢? 搜尋我的枯腸,沒有想出一點什麼適當的東西來,因此也就拖延著") }; let it=b.iter(); for i in it { println!("{}",i); } }
幸運的是(當然也是應該的),rust為許多應該提供迭代器的物件提供了建立迭代器的方法。
例如向量
let mut height_arr: Vec<i32> = Vec::new(); for i in height_arr.iter() { println!("{}", i); }
rust為向量實現了多個方法用於建立迭代器:
iter - 生成一個不可變引用的迭代器
into_iter --生成一個獲取所有權的迭代器
iter_mut --生成一個可變引用的迭代器
例如陣列
fn print_use_for_loop_3_iter(arr:&[u32]){ //注意這個是針對固定陣列,如果是可變陣列則可以 //for element in array.into_iter() println!("---- 使用 for 迴圈和迭代iter 列印陣列元素"); for ele in arr.iter() { print!("{}", ele); } println!("\n"); }
1.3、兩個重要概念:迭代器介面卡和消費介面卡
迭代器介面卡-基於生成迭代器方法產生新的迭代器的方法,也可以迭代器包裝器
消費介面卡-基於迭代器,消費迭代器的方法,目的是基於迭代器產生一些新的結果(非迭代器)
此二者都是方法,通常都屬於迭代器自生的方法,迭代器介面卡生成新的迭代器,消費介面卡則是使用迭代器。
原書給出了很完善和淺顯易懂的例子:
-- 迭代器介面卡 let v1: Vec<i32> = vec![1, 2, 3]; v1.iter().map(|x| x + 1); -- 消費介面卡 fn iterator_sum() { let v1 = vec![1, 2, 3]; let v1_iter = v1.iter(); let total: i32 = v1_iter.sum(); assert_eq!(total, 6); }
1.4、用處
- 核心用處:原始碼看起來更加簡潔一些,因為可以向所謂的函數語言程式設計靠近。
- 次要作用:讓工程師專注於業務邏輯,因為資料怎麼得到,由迭代器處理。 此作用因人而異。
函數語言程式設計有優點有缺點,這是一個程式設計風格。如果你不喜歡,不是非得用不可。
阻礙專案進度的往往不是這些細枝末節,而是諸如需求,設計、測試。
如果一個專案完全不用迭代器也是完全很好的。
不過如果rust官方已經提供了很多的介面卡,那麼該用還是要用的。
例如上文的消費介面卡sum,為什麼不用了?畢竟可以少打幾個字母。
二、效能比較(對比迴圈)
結論:請放心大膽的使用迭代器和閉包吧!它們使得程式碼看起來更高階,但並不為此引入執行時效能損失
這是因為rust的編譯器對匿名函式和迭代器的實現進行了最佳化。按照作者的意思就是:在很多環境中,迭代相對迴圈所損失的效能微乎其微。
2.1、零成本抽象
zero-cost abstractions,中文含義“零成本抽象”
意思就是:雖然我把程式碼寫得更加簡潔(往往可能就是抽象了),但是我透過編譯器得努力,不會因為這個抽象而損失效能
這個所謂的零成本抽象應該是rust的目標之一。
把語法搞得複雜抽象應該也是rust的目標之一,結合零抽象成本,可以告訴工程師放心使用rust。
2.2、rust編譯器
我已經說了很多次:rust厲害的是編譯器。大體是這個意思。
具體怎麼零成本的,作者沒有多說,不過提到一個概念:unroll(展開)
這是一個“笨”辦法,也是提升效能的最常用的一個方法。
這個方法就是把程式碼編譯為多段重複的程式碼,大概如下:
/** * 迴圈,非展開 */ fn roll(){ let arr=[1,2,3]; let mut total=0; for i in 0..arr.len(){ total=total+arr[i]; } println!("{}",total); } /** * 展開測試1 */ fn unroll_one(){ let arr=[1,2,3]; let mut total=0; total=total+arr[0]+arr[1]+arr[2]; println!("{}",total); } /** * 展開測試2 */ fn unroll_two(){ let a=1; let b=2; let c=3; let mut total=0; total=a+b+c; println!("{}",total); } fn main(){ roll(); unroll_one(); unroll_two(); }
rust大概會把上文中的roll改寫為 unroll_two之類的程式碼。當然這僅僅是一個示意,本人沒有研究過rustc的實現。
寫程式要看是處於什麼目的,如果追求工程效率,這樣寫肯定被罵死。
如果最求效能,這反而是相當明智有效的方法之一。
2.3、個人看法
- 如果有rust的預設迭代器,我會考慮用用。如果沒有,我不會特意去定義的
- 出於效能等原因,我更願意使用迴圈,而不是迭代
- 希望rust團隊能給rust新增更多物件導向的內容。物件導向並不會對效能造成什麼影響
三、小結
- 實現特質Iterator就能夠建立自己迭代器
- rust提供了許多預設的迭代器介面卡、迭代器方法
- 利用消費介面卡、迭代器介面卡和匿名函式,能夠實現函數語言程式設計
- 據說rust能夠讓迭代器的效能和for迴圈差不多
- 迭代器是rust實現其零成本抽現象的實踐之一