Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

泛型函數

本集目標

學會用 <T> 定義泛型函數,讓同一個函數可以處理不同型別。

概念說明

在第四章,我們學了 Vec,用它來存一堆 i32。但你有沒有注意到,我們總是寫 vec![1, 2, 3],讓 Rust 自己推斷型別?

其實,Vec 不是一個完整的型別。它的完整寫法是 Vec<i32>Vec<String>Vec<bool>——角括號 <> 裡面放的是「這個 Vec 要裝什麼型別的東西」。

第四章故意沒提角括號,因為當時我們還沒學過泛型。現在,是時候揭開這個秘密了。

什麼是泛型?

假設你想寫一個函數,傳入兩個值,回傳第一個:

fn first_i32(a: i32, b: i32) -> i32 {
    a
}

fn main() {}

如果又需要處理 f64 呢?難道要再寫一個 first_f64

泛型就是解決這個問題的。我們用一個「型別參數」T 來代替具體的型別:

fn first<T>(a: T, b: T) -> T {
    a
}

fn main() {}

這裡的 <T> 寫在函數名後面,表示「這個函數有一個型別參數叫 T」。然後參數 ab 的型別都是 T,回傳值也是 T

當你呼叫 first(10, 20) 的時候,Rust 看到 10i32,就知道 T = i32。呼叫 first(3.14, 2.71) 的時候,T = f64。同一個函數定義,自動適用於不同型別。

命名慣例

型別參數通常用單個大寫字母:T(Type)、UV。如果有語意的話也會用比較長的名字,但目前用 T 就好。

範例程式碼

// 泛型函數:回傳兩個值中的第一個
fn first<T>(a: T, _b: T) -> T {
    a
}

// 可以有多個型別參數
fn make_pair<T, U>(a: T, b: U) -> (T, U) {
    (a, b)
}

fn main() {
    // T 被推斷為 i32
    let x = first(10, 20);
    println!("{}", x);

    // T 被推斷為 &str
    let y = first("hello", "world");
    println!("{}", y);

    // 但兩個參數的型別必須一樣,因為 first 的兩個參數都是 T
    // let bad = first(1, "a"); // 編譯錯誤!1 是 i32 但 "a" 是 &str

    // T = i32, U = &str
    let pair = make_pair(42, "hello");
    println!("{:?}", pair);
}

重點整理

  • Vec 的完整寫法是 Vec<T>,角括號裡放型別參數——第四章故意省略,現在正式學習
  • 泛型函數用 <T> 宣告型別參數,讓同一個函數可以處理不同型別
  • Rust 會根據傳入的值自動推斷 T 是什麼型別
  • 可以有多個型別參數:<T, U>
  • 型別參數慣例用大寫字母:TUV