泛型函數
本集目標
學會用 <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」。然後參數 a 和 b 的型別都是 T,回傳值也是 T。
當你呼叫 first(10, 20) 的時候,Rust 看到 10 是 i32,就知道 T = i32。呼叫 first(3.14, 2.71) 的時候,T = f64。同一個函數定義,自動適用於不同型別。
命名慣例
型別參數通常用單個大寫字母:T(Type)、U、V。如果有語意的話也會用比較長的名字,但目前用 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> - 型別參數慣例用大寫字母:
T、U、V