rust實戰系列 - 使用Iterator 迭代器實現斐波那契數列(Fibonacci )

yihailin發表於2022-02-05

為什麼是斐波那契數列

斐波那契數列十分適合用來實戰rust的迭代器,演算法也很簡單,一目瞭然。這個例子可以用來學習Iterator的使用,十分適合剛學習了rust的迭代器章節後用來練練手。

程式碼實戰

don't bb, show me the code

struct Fib(usize, usize);

impl Fib {
    fn new() -> Fib {
        Fib(0, 1)
    }
}

impl Iterator for Fib {
    type Item = usize;
    fn next(&mut self) -> Option<usize> {
        *self = Fib(self.1, self.0 + self.1);
        Some(self.0)
    }
}

fn main() {
    let last = 20;
    println!("fib({}) result: {:?}", last, Fib::new().take(last).collect::<Vec<usize>>());
}

分解知識點

  1. 程式碼定義了一個名字為Fib的元組結構體(tuple structs)。因為我們的實現封裝了實現細節,沒必要定義一個具名結構體。
    網上有給出其他定義了名稱的結構體,個人覺得有點多餘了。比如這樣
struct Fibonacci {
    a: u64,
    b: u64,
}
  1. 第二點就是如何實現Iterator,關鍵就兩點,定義關聯型別和實現next方法
impl Iterator for Fib {
    // 1. 定義關聯型別為usize
    type Item = usize;
    // 2. 實現next方法,這裡也是主要的邏輯
    fn next(&mut self) -> Option<usize> {
        *self = Fib(self.1, self.0 + self.1);
        Some(self.0)
    }
}
  1. 第三點是*self = Fib(self.1, self.0 + self.1);; self被定義為了可變引用(&mut), 這裡*self 解引用為Fib型別。
    另外一種寫法
self = &mut Fib(self.1, self.0 + self.1);

上面定義了一個可變引用 &mut Fib 賦值給self,rust編譯器直接提示

   |
12 |         self = &mut Fib(self.1, self.0 + self.1);
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ cannot assign to immutable argument

error[E0716]: temporary value dropped while borrowed
  --> src\main.rs:12:21
   |
11 |     fn next(&mut self) -> Option<usize> {
   |             - let's call the lifetime of this reference `'1`
12 |         self = &mut Fib(self.1, self.0 + self.1);
   |         ------------^^^^^^^^^^^^^^^^^^^^^^^^^^^^- temporary value is freed at the end of this statement
   |         |           |
   |         |           creates a temporary which is freed while still in use
   |         assignment requires that borrow lasts for `'1`

提示借用了臨時變數,臨時變數會被丟棄掉。其實就是&mut Fib 只是一個可變借用,在被借用給self後就會馬上失效了,self就會指向一個懸垂指標, rust編譯器肯定不會讓這種情況發生的(強行解釋一波,歡迎打臉)。
所以正確的做法是直接讓self獲取新建立的Fib的所有權就行了。也就是*self = Fib(self.1, self.0 + self.1);

  1. 第四點是Fib::new().take(last).collect::<Vec<usize>>()。 這裡直接在println巨集裡列印結果,編譯器推斷不出需要collect的型別,需要使用collect::標註。
    除非是下面這種寫法,編譯器能自動推斷出來
let result: Vec<usize> = Fib::new().take(last).collect();
println!("fib({}) result: {:?}", last, result);

總結

本文通過rust iterator來實現斐波那契數列,需要掌握一下要點

  • 元組結構體寫法
  • 如何實現iterator trait
  • collect::<B> 幫助編譯器推斷型別

相關文章