Rust程式語言融合了多種程式設計法(programming paradigm),其中的指令式程式設計(imperative programming)所用的迴圈和函數式程式設計(functional programming)所提供的迭代器(iterator)可以加強陣列或是切片的走訪效能。然而,在走訪陣列或是切片時,迭代器在每次迭代時只會回傳元素的值或參考,而不會將索引值也跟著回傳出來。這個問題雖然可以透過迭代器的enumerate方法來輕鬆解決,enumerate會讓迭代器回傳目前是第幾次的迭代(從0開始數),這個次數值可以當作是目前走訪到的元素的索引值,只是這樣的作法會對程式效能有什麼不良的影響呢?



我們先來看看以下程式碼:

let array = [2, 2, 3, 4, 5, 6, 7, 8];

for i in 0..array.len() {
    println!("array[{}] = {}", i, array[i]);
}

以上程式,會利用For計次迴圈來走訪array這個存在於堆疊內的陣列,並將其所有索引值所對應的元素值通通印在螢幕上。

先前的文章中,我們已經知道用For迴圈來走訪堆疊內的陣列,效能跟和用For迭代器迴圈或是迭代器是一樣的。那麼如果現在我們是將以上程式改用For迭代器迴圈和enumerate方法來完成呢?程式如下:

let array = [2, 2, 3, 4, 5, 6, 7, 8];

for (i, &n) in array.iter().enumerate() {
    println!("array[{}] = {}", i, n);
}

透過enumerate方法所二度產生出來的迭代器,會影響到效能嗎?

效能實測

直接實際寫一段程式來測試運算效能吧!這段程式可以在GitHub上取得:

https://github.com/magiclen/rust-performance-measurement/blob/master/benches/iter_enumerate.rs

根據測試結果,可以發現使用enumerate方法和使用For計次迴圈的效能是差不多的,所以可以放心使用~

enumerate方法所產生出來的迭代器和原本的只差在前者用Enumerate結構體包裹了後者,並且多了一個計次變數欄位,在每次呼叫next方法時都會把該計次變數的值以數組(tuple)的方式與元素儲存在一起,然後在計次變數加一後,回傳該數組。next方法的程式碼如下:

fn next(&mut self) -> Option<(usize, <I as Iterator>::Item)> {
    self.iter.next().map(|a| {
        let ret = (self.count, a);
        self.count += 1;
        ret
    })
}

所以它的效能才會和For計次迴圈差不多。而如果走訪的對象不是堆疊內的陣列,它的效能還會比For迴圈更好呢!