泛型型別、特徵與生命週期

每個程式語言都有能夠高效處理概念複製的工具。在 Rust 此工具就是泛型(generics):實際型別或其他屬性的抽象替代。我們可以表達泛型的行為,或是它們與其他泛型有何關聯,而不必在編譯與執行程式時知道它們實際上是什麼。

函式也可以接受一些泛型型別參數,而不是實際型別像是 i32String,就像函式有辦法能接收多種未知數值作為參數來執行相同程式碼。事實上我們已經在第六章的 Option<T>、第八章的 Vec<T>HashMap<K, V> 以及第九章的 Result<T, E> 使用過泛型了。在本章節,你將會探索如何用泛型定義你自己的型別、函式與方法!

首先我們會先檢視如何提取參數來減少重複的程式碼。接著我們會以相同的技巧使用泛型將兩個只有參數型別不同的函式轉變成泛型函式。我們還會解釋如何在結構體和列舉使用泛型型別。

再來你會學會如何使用特徵(traits) 來定義共同行為。你可以組合特徵與泛型型別來限制泛型型別只適用在有特定行為的型別,而不是任意型別。

最後我們會來介紹生命週期(lifetimes):一種能讓編譯器知道參考如何互相關聯的泛型。生命週期讓我們能提供給編譯器更多關於借用數值的資訊,好讓它在更多情況下可以確保參考是有效的。

提取函數來減少重複性

泛型讓我們可以用佔位符(placeholder)替代特定型別,來表示多重型別並減少程式碼的重複性。在我們深入泛型語法之前,讓我們先來看如何不用泛型型別的情況下,用提取函式的方式減少重複的程式碼。之後我們就會用相同的方式來提取泛型函式!和你透過找出重複的程式碼來提取程式一樣,你也將找出重複的函式來轉成泛型。

我們先從範例 10-1 中一支尋找列表中最大數字的小程式開始。

檔案名稱:src/main.rs

fn main() {
    let number_list = vec![34, 50, 25, 100, 65];

    let mut largest = &number_list[0];

    for number in &number_list {
        if number > largest {
            largest = number;
        }
    }

    println!("最大數字為 {}", largest);
    assert_eq!(*largest, 100);
}

範例 10-1:在數字列表中尋找最大數字的程式碼

我們儲存整數列表到變數 number_list 並將列表第一個數字的參考放入變數 largest。接著我們遍歷列表中的所有元素,如果目前數字比 largest 內儲存的數字還大的話,就會替代成該變數的參考。不過如果目前數值小於或等於最大值的話,變數就不會被改變,程式會接續檢查列表中的下一個數字。在考慮完列表中的所有數字後,largest 就應該會指向最大數字,在此例就是 100。

現在我們要從兩個不同的數字列表中找到最大值,我們可以重複範例 10-1 的程式碼,然後在程式中兩個不同的地方使用相同的邏輯,如範例 10-2 所示。

檔案名稱:src/main.rs

fn main() {
    let number_list = vec![34, 50, 25, 100, 65];

    let mut largest = &number_list[0];

    for number in &number_list {
        if number > largest {
            largest = number;
        }
    }

    println!("最大數字為 {}", largest);

    let number_list = vec![102, 34, 6000, 89, 54, 2, 43, 8];

    let mut largest = &number_list[0];

    for number in &number_list {
        if number > largest {
            largest = number;
        }
    }

    println!("最大數字為 {}", largest);
}

範例 10-2:在兩個數字列表中尋找最大值

雖然這樣的程式碼能執行,寫出重複的程式碼很囉唆而且容易出錯。我們還得記住每次更新時就得一起更新各個地方。

要去除重複的部分,我們可以建立一層抽象,定義一個可以處理任意整數列表作為參數的函式。這樣的解決辦法讓我們的程式更清晰,而且讓我們能抽象表達出從列表中尋找最大值這樣的概念。

在範例 10-3 我們提取了尋找最大值的程式碼成一個函式叫做 largest。然後我們呼叫函式來尋找範例 10-2 兩個列表中最大的數字。我們還可以在未來對其他任何 i32 的列表使用此函式。

檔案名稱:src/main.rs

fn largest(list: &[i32]) -> &i32 {
    let mut largest = &list[0];

    for item in list {
        if item > largest {
            largest = item;
        }
    }

    largest
}

fn main() {
    let number_list = vec![34, 50, 25, 100, 65];

    let result = largest(&number_list);
    println!("最大數字為 {}", result);
    assert_eq!(*result, 100);

    let number_list = vec![102, 34, 6000, 89, 54, 2, 43, 8];

    let result = largest(&number_list);
    println!("最大數字為 {}", result);
    assert_eq!(*result, 6000);
}

範例 10-3:抽象出尋找最大值的概念並用在兩個不同的列表

largest 函式有個參數 list 可以代表我們傳遞給函式的 i32 型別切片。所以當我們呼叫此函式時,程式可以依據我們傳入的特定數值執行。

總結來說,以下是我們將範例 10-2 的程式碼轉換成範例 10-3 的步驟:

  1. 找出重複的程式碼。
  2. 將重複的程式碼提取置函式本體內,並指定函式簽名輸入與回傳數值。
  3. 更新重複使用程式碼的實例,改呼叫我們定義的函式。

接著我們將以相同的步驟使用泛型來減少重複的程式碼。就像函式本體可以抽象出 list 而不用特定數值,泛型允許程式碼執行抽象型別。

舉例來說,假設我們有兩個函式:一個會找出 i32 型別切片中的最大值而另一個會找出 char 型別切片的最大值。我們要如何刪除重複的部分呢?讓我們拭目以待!