泛型

在函式中,你要寫出拿什麼型別作為輸入:

fn return_number(number: i32) -> i32 {
    println!("Here is your number.");
    number
}

fn main() {
    let number = return_number(5);
}

但是如果你想用的不僅僅是 i32 呢?你可以用泛型(Generics)來解決。泛型的意思是 "也許是某一種型別,也許是另一種型別"。

泛型的寫法要用角括號裡面加上型別,像這樣:<T> 這個意思是"你放進函式的任意型別"。通常泛型會使用一個大寫字母的型別(T、U、V等),儘管你不必只使用一個字母。

這個範例是你如何改變函式讓它用泛型:

fn return_number<T>(number: T) -> T {
    println!("Here is your number.");
    number
}

fn main() {
    let number = return_number(5);
}

重點是函式名稱後的 <T>。如果沒有這個,Rust 會認為 T 是一個具體的(concrete,具體的 = 不是泛型的)型別,像是 Stringi8

如果我們能寫出型別名,就更容易理解了。看看我們把 T 改成 MyType 會發生什麼:

#![allow(unused)]
fn main() {
fn return_number(number: MyType) -> MyType { // ⚠️
    println!("Here is your number.");
    number
}
}

大家可以看到,MyType 是具體的,不是泛型的。所以我們需要寫成這樣,它現在就可以執行了:

fn return_number<MyType>(number: MyType) -> MyType {
    println!("Here is your number.");
    number
}

fn main() {
    let number = return_number(5);
}

所以單字母 T 是給人眼看的,但函式名稱後的部分是給編譯器的"眼睛"看的。沒有了它,就不是泛型了。

現在我們再回到型別 T,因為 Rust 程式碼通常使用 T

你會記得 Rust 中有些型別是 Copy,有些是 Clone,有些是 Display,有些是 Debug,等等。有 Debug,我們可以用 {:?} 來列印。所以現在大家可以看到,我們如果要印出 T 就有問題了:

fn print_number<T>(number: T) {
    println!("Here is your number: {:?}", number); // ⚠️
}

fn main() {
    print_number(5);
}

print_number 需要 Debug 印出 number,但是 T 是一個有 Debug 的型別嗎?也許不是。也許它沒有 #[derive(Debug)],誰知道呢?編譯器也不知道,所以它給了錯誤:

error[E0277]: `T` doesn't implement `std::fmt::Debug`
  --> src\main.rs:29:43
   |
29 |     println!("Here is your number: {:?}", number);
   |                                           ^^^^^^ `T` cannot be formatted using `{:?}` because it doesn't implement `std::fmt::Debug`

T 沒有實作 Debug。那麼我們是否要為 T 實現 Debug 呢?不,因為我們不知道(具體的) T 是什麼。但是我們可以告訴函式:"別擔心,因為這個函式用的任何 T 型別都會有 Debug"

use std::fmt::Debug; // 聲明 Debug 是來自 std::fmt::Debug。所以後面我們可以只寫 'Debug'。

fn print_number<T: Debug>(number: T) { // <T: Debug> 是重點
    println!("Here is your number: {:?}", number);
}

fn main() {
    print_number(5);
}

所以現在編譯器知道:"好的,這個型別 T 會有 Debug"。現在程式碼執行了,因為 i32 有 Debug。現在我們可以給它很多型別。String&str 等,因為它們都有 Debug.

現在我們可以建立結構,並用 #[derive(Debug)] 給它實作 Debug,所以現在我們也可以印出它。我們的函式能接受 i32、Animal 結構體及更多型別:

use std::fmt::Debug;

#[derive(Debug)]
struct Animal {
    name: String,
    age: u8,
}

fn print_item<T: Debug>(item: T) {
    println!("Here is your item: {:?}", item);
}

fn main() {
    let charlie = Animal {
        name: "Charlie".to_string(),
        age: 1,
    };

    let number = 55;

    print_item(charlie);
    print_item(number);
}

印出:

Here is your item: Animal { name: "Charlie", age: 1 }
Here is your item: 55

有時我們在泛型函式中需要不止一種型別。我們必須寫出每個型別的名稱,並思考我們想要如何使用它。在這個範例中,我們想要兩個型別。首先我們想印出型別為 T 的陳述式。用 {} 列印更好,所以我們會要求用 Display 來列印 T

下個是型別 U 和 num_1num_2 這兩個型別為 U(U 是某種數字)的變數。我們想要比較它們,所以我們需要 PartialOrd。這個特性讓我們可以使用 <>== 等。我們也想印出它們,所以我們也要求有 Display 來印出 U

use std::fmt::Display;
use std::cmp::PartialOrd;

fn compare_and_display<T: Display, U: Display + PartialOrd>(statement: T, num_1: U, num_2: U) {
    println!("{}! Is {} greater than {}? {}", statement, num_1, num_2, num_1 > num_2);
}

fn main() {
    compare_and_display("Listen up!", 9, 8);
}

印出 Listen up!! Is 9 greater than 8? true

所以 fn compare_and_display<T: Display, U: Display + PartialOrd>(statement: T, num_1: U, num_2: U) 說得是:

  • 函式名稱是 compare_and_display
  • 第一個型別是泛型的 T。它必須是一個可以用 {} 列印的型別。
  • 下一個型別是泛型的 U。它必須是一個可以用 {} 列印的型別。另外,它必須是一個可以比較的型別(使用 ><==)。

現在我們可以給 compare_and_display 不同的型別。statement 可以是 String&str,或任何有 Display 的型別。

為了讓泛型函式更容易讀懂,我們也可以這樣寫得像這個範例,在程式碼區塊之前用 where

use std::cmp::PartialOrd;
use std::fmt::Display;

fn compare_and_display<T, U>(statement: T, num_1: U, num_2: U)
where
    T: Display,
    U: Display + PartialOrd,
{
    println!("{}! Is {} greater than {}? {}", statement, num_1, num_2, num_1 > num_2);
}

fn main() {
    compare_and_display("Listen up!", 9, 8);
}

尤其當你有很多泛型型別時,使用 where 是一個好主意。

還要注意:

  • 如果你有一個型別 T 和另一個型別 T,它們必須是相同的。
  • 如果你有一個型別 T 和另一個型別 U,它們可以是不同的。但它們也可以是相同的。

比如說:

use std::fmt::Display;

fn say_two<T: Display, U: Display>(statement_1: T, statement_2: U) { // T型別要有 Display,U型別要有 Display
    println!("I have two things to say: {} and {}", statement_1, statement_2);
}

fn main() {

    say_two("Hello there!", String::from("I hate sand.")); // T型別是 &str,但U型別是 String。
    say_two(String::from("Where is Padme?"), String::from("Is she all right?")); // 兩者型別皆是 String。
}

印出:

I have two things to say: Hello there! and I hate sand.
I have two things to say: Where is Padme? and Is she all right?