泛型
在函式中,你要寫出拿什麼型別作為輸入:
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,具體的 = 不是泛型的)型別,像是 String
或 i8
。
如果我們能寫出型別名,就更容易理解了。看看我們把 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_1
和 num_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?