rust十三.2、迭代器

正在战斗中發表於2024-12-10

Iterator(迭代器)是一個比較常見的概念,大部分語言都有。大部分語言實現迭代器的邏輯並沒有特別的。

看完了有關內容,作者的意思是:rust的迭代器和匿名函式一樣,都是為了提供時下流行的函數語言程式設計。

此二者為rust實現零成本抽象提供了不少的貢獻。

本部分概念比較多,如果是程式設計初學者可能會有點吃力,如果是老手還是比較容易理解的。

一、迭代器基本概念

問題:

  1. 迭代器是什麼
  2. 如何建立
  3. 迭代器佑什麼用

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為向量實現了多個方法用於建立迭代器:

  1. iter - 生成一個不可變引用的迭代器
  2. into_iter --生成一個獲取所有權的迭代器
  3. 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、個人看法

  1. 如果有rust的預設迭代器,我會考慮用用。如果沒有,我不會特意去定義的
  2. 出於效能等原因,我更願意使用迴圈,而不是迭代
  3. 希望rust團隊能給rust新增更多物件導向的內容。物件導向並不會對效能造成什麼影響

三、小結

  1. 實現特質Iterator就能夠建立自己迭代器
  2. rust提供了許多預設的迭代器介面卡、迭代器方法
  3. 利用消費介面卡、迭代器介面卡和匿名函式,能夠實現函數語言程式設計
  4. 據說rust能夠讓迭代器的效能和for迴圈差不多
  5. 迭代器是rust實現其零成本抽現象的實踐之一

相關文章