Rust 學習筆記:特徵
2023年10月18日 · 中文
前言
本系列文書寫我個人初探 Rust 的學習筆記,章節劃分主要基於著名的 The Book,The Rust Programming Language,程式碼部分通常是個人閱讀消化後以方便說明的方式撰寫,完整學習建議直接參見該書。
The Rust Programming Language
該書也有中文翻譯版,不過個人閱讀以英文原版為主以鞏固對 terminology 的一致認識,我認為對未來閱讀以及查找資料會較為順暢。
不論語言該書都是相當優秀的學習資源,選擇適合你的語言開始學習 Rust 吧。
Appendix F: Translations of the Book
特徵 Traits
特徵用於定義共同行為,類似於某些語言中 interface 的用途,但有些不同。
定義
定義 trait 的寫法如下:
trait Greeting { fn say_hello(&self) -> String; }
和 impl
區塊非常像,不過這邊不寫方法的實作,而是提供簽名 (signatures),定義方法的名字、參數與回傳值。
實作
這邊是為 struct
實作 trait 的例子,在 impl
後面加上 for
以實作特徵。
trait Greeting { fn say_hello(&self) -> String; } struct Person { name: String } impl Greeting for Person { fn say_hello(&self) -> String { format!("Hello {}!", self.name) } } fn main() { let p1 = Person { name: String::from("Josh") }; println!("{}", p1.say_hello()); // Hello Josh! }
要注意的是實作必須包含所有 trait 所定義的簽名,否則會出現錯誤。
trait Greeting { fn say_hello(&self) -> String; fn say_goodbye(&self) -> String; // added } struct Person { name: String } impl Greeting for Person { fn say_hello(&self) -> String { format!("Hello {}!", self.name) } } // ERROR: not all trait items implemented, missing: `say_bye`
預設實作
在 trait block 中並非不能撰寫實作部分,寫在 trait 中的實作會被用為預設實作。
trait Greeting { fn say_hello(&self) -> String; fn say_goodbye(&self) -> String { String::from("Good bye!") } } struct Person { name: String } impl Greeting for Person { fn say_hello(&self) -> String { format!("Hello {}!", self.name) } } fn main() { let p1 = Person { name: String::from("Josh") }; println!("{}", p1.say_goodbye()); // Good bye! }
用 trait 定參數
我們可以利用 trait 來定義參數的型別為有實作特定 trait 的類型,寫為 impl Trait
如下。
fn print_hello(p: &impl Greeting) { println!("{}", p.say_hello()); }
上面可以改用 trait bound 的寫法寫成下面這樣,這在介紹到泛型時用過。
fn print_hello<T: Greeting>(p: &T) { println!("{}", p.say_hello()); }
進階使用
- 可以使用
+
來指定複數個 trait - 使用
where
語法定義 trait bound,避免冗長的泛型定義
下面程式碼建立了兩個 struct,其中 Person
實作了 Hello
和 Goodbye
trait,P
則只實作 Hello
,利用上面兩個特性定義 greeting
函式。
struct Person { name: String } struct P { name: String } trait Hello { fn say_hello(&self) -> String { String::from("Hello!") } } trait Goodbye { fn say_goodbye(&self) -> String { String::from("Good bye!") } } impl Hello for Person {} impl Goodbye for Person {} impl Hello for P {} fn greeting<T, U>(p1: &T, p2: &U) where T: Hello + Goodbye, U: Hello { println!("{}", p1.say_hello()); println!("{}", p1.say_goodbye()); println!("{}", p2.say_hello()); } fn main() { let p1 = Person { name: String::from("Josh") }; let p2 = P { name: String::from("Josh") }; greeting(&p1, &p2); // output: // Hello! // Good bye! // Hello! }
對所有有符合特徵的類型實作特徵
下面程式為有實作 Hello
的類型實作 PrintHello
trait。
struct Person { name: String } trait Hello { fn say_hello(&self) -> String { String::from("Hello!") } } trait PrintHello { fn print_hello(&self); } impl Hello for Person {} impl<T: Hello> PrintHello for T { fn print_hello(&self){ println!("{}", self.say_hello()); } } fn main() { let p1 = Person { name: String::from("Josh") }; p1.print_hello(); // Hello! }
寫在最後
泛型與特徵結合使用的技巧很多,這邊只有截選一些個人的筆記,掌握兩者並結合使用是減少程式碼重複的關鍵,也讓別人更易於使用自己寫的程式。
下一章是 Rust 中非常重要也非常不容易的 topic,生命週期 (lifetime)。