?? 歡迎大家來到景天科技苑??
🎈🎈 養成好習慣,先贊后看哦~🎈🎈
🏆 作者簡介:景天科技苑
🏆《頭銜》:大廠架構師,華為云開發者社區專家博主,阿里云開發者社區專家博主,CSDN全棧領域優質創作者,掘金優秀博主,51CTO博客專家等。
🏆《博客》:Rust開發,Python全棧,Golang開發,云原生開發,PyQt5和Tkinter桌面開發,小程序開發,人工智能,js逆向,App逆向,網絡系統安全,數據分析,Django,fastapi,flask等框架,云原生K8S,linux,shell腳本等實操經驗,網站搭建,數據庫等分享。所屬的專欄:Rust語言通關之路
景天的主頁:景天科技苑
文章目錄
- Rust trait(特征)
- 1. Trait基礎概念
- 1.1 什么是Trait
- 1.2 Trait與接口的區別
- 1.3 Trait的基本語法
- 2. Trait實現與使用
- 2.1 為類型實現Trait
- 2.2 Trait的多種用法
- 1. Trait作為參數
- 2. 默認實現
- 3. Trait Bound泛型約束
- 4. trait作為返回值
- 5. 注意事項
- 3. 標準庫中的常用Trait
- 3.1 格式化相關Trait
- 3.2 轉換Trait
- 3.3 運算符重載Trait
- 3.4 其他比較重要的內置trait
Rust trait(特征)
1. Trait基礎概念
1.1 什么是Trait
在Rust中,Trait是一種定義共享行為的機制,類似于其他語言中的"接口"(interface)或"抽象類"(abstract class)。
Trait允許我們定義一組方法簽名,這些方法可以被不同類型實現,從而實現多態行為。
1.2 Trait與接口的區別
雖然Trait類似于接口,但Rust的Trait更加靈活和強大:
關聯類型:Trait可以定義關聯類型
默認實現:方法可以有默認實現
Trait對象:支持動態分發
條件實現:可以根據類型參數有條件地實現Trait
1.3 Trait的基本語法
定義Trait的基本語法如下:
trait里面定義各種方法
trait TraitName {fn method1(&self, ...) -> ReturnType;fn method2(&mut self, ...) -> ReturnType;// 可以有默認實現fn method_with_default(&self) {println!("Default implementation");}
}
使用 trait 關鍵字來聲明一個 trait,后面是 trait 的名字,在這個例子中是 TraitName 。
在大括號中聲明描述實現這個trait 的類型所需要的行為的方法,在這個例子中有三個方法 。
在方法簽名后跟分號,而不是在大括號中提供其實現。
接著每一個實現這個 trait 的類型都需要提供其自定義行為的方法體,編譯器也會確保任何實現TraitName trait 的類型都擁有與這個簽名的定義完全一致的方法。
trait 體中可以有多個方法,一行一個方法簽名且都以分號結尾。
2. Trait實現與使用
2.1 為類型實現Trait
我們可以為任何類型實現Trait,包括標準庫類型和我們自定義的類型。
實現trait
impl 關鍵字之后,我們提供需要實現 trait 的名稱,接著是for 和需要實現 trait 的 類型的名稱。
在 impl 塊中,使用 trait 定義中的方法簽名,不過不再后跟分號,而是需要在大括號中編寫函數體來為特定類型實現 trait 方法所擁有的行為。
語法如下:
impl trait for 類型名 {}
注意:實現 trait時,方法的簽名必須與 trait 中定義的方法一致。trait 中的方法簽名包括方法名、參數類型和返回值類型。
實現trait,必須將trait中的方法全部實現,默認實現的方法除外
不能自定義方法的簽名
//定義一個 trait 和一個實現了該 trait 的結構體
// 這個 trait 定義了一個方法 say_hello
// 結構體 Person 實現了這個 trait
// 通過實現 trait 的方法,我們可以在結構體實例上調用這個方法
// 這個例子展示了如何使用 trait 來定義行為
// 以及如何在結構體中實現這些行為
// 通過 trait,我們可以定義一組方法的簽名
// 然后在不同的結構體中實現這些方法
// 這使得代碼更加靈活和可擴展
// 通過 trait,我們可以實現多態
trait Greet {fn say_hello(&self);fn say_goodbye(&self);
}// 定義一個結構體 Person
struct Person {name: String,
}// 實現 trait Greet 的方法
// 為結構體 Person 實現 trait Greet
// 通過實現 trait 的方法,我們可以在結構體實例上調用這個方法
impl Greet for Person {//注意:實現 trait時,方法的簽名必須與 trait 中定義的方法一致。trait 中的方法簽名包括方法名、參數類型和返回值類型。//不能自定義方法的簽名fn say_hello(&self) {println!("Hello, my name is {}!", self.name);}fn say_goodbye(&self) {println!("Goodbye, {}!", self.name);}
}fn main() {// 創建一個 Person 實例let alice = Person { name: "jingtian".to_string() };// 調用 trait 中定義的方法// 通過結構體實例調用 trait 中的方法alice.say_hello(); // 輸出: Hello, my name is Alice!alice.say_goodbye(); // 輸出: Goodbye, Alice!
}
2.2 Trait的多種用法
1. Trait作為參數
傳參的時候,我們并不知道item的具體類型
但是我們知道它實現了Summary這個trait
所以我們可以把它當做Summary來使用
只要某個類,實現了這個特征,這個類的實例就可以調用特征里面的方法
trait Summary {fn summarize(&self) -> String;fn summarize_author(&self) -> String;// 可以有默認實現fn summarize_default(&self) -> String {format!("(Read more from {}...)", self.summarize_author())}
}struct NewsArticle {headline: String,location: String,
}
impl Summary for NewsArticle {fn summarize(&self) -> String {format!("{} - {}", self.headline, self.location)}fn summarize_author(&self) -> String {String::from("jingtian")}
}//trait作為參數
//傳參的時候,我們并不知道item的具體類型
//但是我們知道它實現了Summary這個trait,實現了這個特征的類型的對象,就可以作為參數傳進來
//所以我們可以把它當做Summary來使用
fn notify(item: &impl Summary) {println!("Breaking news! {}", item.summarize());
}fn main() {println!("Hello, world!");//調用 trait 作為參數的函數let article = NewsArticle {headline: String::from("Rust is awesome!"),location: String::from("San Francisco"),};notify(&article);//調用 trait 的默認實現println!("{}", article.summarize_default());
}
2. 默認實現
trait的默認實現,就是在定義trait的時候,將里面的函數體寫出來,而不是簡單的只定義一個函數簽名
這樣,當某個類型實現了這個trait,不用再去寫具體的方法內容,就可以調用這個trait的方法
Trait方法可以有默認實現,實現者可以選擇使用默認實現或覆蓋它。
有時為 trait 中的某些或全部方法提供默認的行為,而不是在每個類型的每個實現中都定義自己的行為是很有用的。
這樣當為某個特定類型實現 trait 時,可以選擇保留或重載每個方法的默認行為。
//trait的默認實現
trait Summary {fn summarize(&self) -> String;fn summarize_author(&self) -> String;// 在定義trait的時候,就將方法體給實現的方法,稱為默認實現fn summarize_default(&self) -> String {format!("(Read more from {}...)", self.summarize_author())}
}struct NewsArticle {headline: String,location: String,
}//可以看到我們實現trait的時候,并沒有實現默認的方法
// 但是我們可以直接使用默認的方法
// 這就是 trait 的默認實現的好處
impl Summary for NewsArticle {fn summarize(&self) -> String {format!("{} - {}", self.headline, self.location)}fn summarize_author(&self) -> String {String::from("jingtian")}
}fn main() {let article = NewsArticle {headline: String::from("Rust is awesome!"),location: String::from("San Francisco"),};println!("{}", article.headline);println!("{}", article.location);//調用實現了 trait 的方法println!("實現了的方法: {}", article.summarize());//調用 trait的默認實現println!("trait的默認實現: {}", article.summarize_default());
}
當然,我們也可以不使用默認的實現,將默認的實現給改成自己的實現
將默認實現的函數重寫
//trait的默認實現
trait Summary {fn summarize(&self) -> String;// 可以有默認實現fn summarize_author(&self) -> String;fn summarize_default(&self) -> String {format!("(Read more from {}...)", self.summarize_author())}
}struct NewsArticle {headline: String,location: String,
}//可以看到我們實現trait的時候,并沒有實現默認的方法
// 但是我們可以直接使用默認的方法
// 這就是 trait 的默認實現的好處
impl Summary for NewsArticle {fn summarize(&self) -> String {format!("{} - {}", self.headline, self.location)}fn summarize_author(&self) -> String {String::from("jingtian")}//將默認實現的函數重寫fn summarize_default(&self) -> String {format!("(Read more from {}...)", "jingtian".to_string())}
}fn main() {let article = NewsArticle {headline: String::from("Rust is awesome!"),location: String::from("San Francisco"),};println!("{}", article.headline);println!("{}", article.location);//調用實現了 trait 的方法println!("實現了的方法: {}", article.summarize());//調用 重載后的實現println!("trait的默認實現: {}", article.summarize_default());
}
此時,就是我們重載后的方法實現
這樣其實就是實現了多態
3. Trait Bound泛型約束
Rust 中的 trait bound 是一種對泛型類型添加約束的機制,用來確保某個類型實現了特定的 trait,這樣我們就可以在函數或結構體中安全地使用該 trait 的方法或功能。
📘 1)什么是 trait bound?
在 Rust 中,trait 類似于其他語言中的接口,它定義了一組方法簽名。trait bound 就是用來約束泛型類型必須實現某個 trait 的方式。
示例1:
// 定義一個 trait
trait Printable {fn print(&self);
}// 使用 trait bound 約束 T 必須實現 Printable
fn print_item<T: Printable>(item: T) {item.print();
}
示例2:
//trait bound
// trait bound 是 Rust 中的一種語法,用于指定泛型類型參數必須實現某個 trait
// trait bound 可以用于函數、結構體、枚舉等的定義中
// trait bound 的語法是:<T: Trait>,其中 T 是泛型類型參數,Trait 是 trait 的名稱
// trait bound 的作用是限制泛型類型參數的類型,使得在使用泛型時,編譯器可以檢查泛型類型參數是否實現了指定的 trait
trait Summary {fn summarize(&self) -> String;
}
struct NewsArticle {headline: String,location: String,
}
impl Summary for NewsArticle {fn summarize(&self) -> String {format!("{} - {}", self.headline, self.location)}
}fn notify(item: &impl Summary) {println!("Breaking news! {}", item.summarize());
}
//使用 trait bound簡寫
//約束,只有實現了Summary trait的類型才能作為參數傳入
fn notify_bound<T: Summary>(item: &T) {println!("trait_bound Breaking news! {}", item.summarize());
}fn main() {let article = NewsArticle {headline: String::from("Rust is awesome!"),location: String::from("San Francisco"),};//調用 trait 作為參數的函數notify(&article);//調用 trait bound 的函數notify_bound(&article);
}
🧩 2)trait bound 的幾種語法
- 簡寫語法:T: Trait
fn do_something<T: Trait1 + Trait2>(val: T) {// T 實現了 Trait1 和 Trait2
}
- where 語法(更清晰,適用于復雜約束)
fn do_something<T, U>(t: T, u: U)
whereT: Trait1 + Trait2,U: Trait3,
{// 可以使用 T 和 U 的 trait 方法
}
- 用于結構體或枚舉中
struct Wrapper<T: Printable> {value: T,
}
或者使用 where:
struct Wrapper<T>
whereT: Printable,
{value: T,
}
🛠? 3)trait bound 的常見用途
- 約束泛型函數
fn compare<T: PartialOrd>(a: T, b: T) -> T {if a < b { a } else { b }
}
- 實現泛型 trait
impl<T: Display> ToString for Wrapper<T> {fn to_string(&self) -> String {format!("{}", self.value)}
}
- 配合 impl Trait 簡化語法(Rust 2018+)
fn print_item(item: impl Printable) {item.print();
}
這等價于 fn print_item<T: Printable>(item: T)
。
🛠? 4)通過 trait bound 有條件地實現方法
在 Rust 中,可以通過 Trait Bound 為泛型類型有條件地實現方法,這意味著只有當類型滿足特定約束時,這些方法才可用。
這是一種非常強大的模式,允許你為特定類型的子集提供額外功能。
通過使用帶有 trait bound 的泛型 impl 塊,可以有條件的只為實現了特定 trait 的類型實現方法。
基本語法
struct Wrapper<T>(T);// 為所有類型 T 實現的方法
impl<T> Wrapper<T> {fn new(value: T) -> Self {Wrapper(value)}
}// 只為實現了 Display 的類型 T 實現的方法
impl<T: std::fmt::Display> Wrapper<T> {fn display(&self) {println!("Wrapper contains: {}", self.0);}
}
實際應用示例
- 為實現了特定 trait 的類型添加方法
use std::fmt::Debug;struct Printer<T>(T);// 無條件實現的方法
impl<T> Printer<T> {fn new(value: T) -> Self {Printer(value)}
}//有條件實現的方法
// 這里的 T 必須實現 Debug trait
// 這意味著我們可以在這個方法中使用 {:?} 來打印 T 的值
// 這使得我們可以在實現方法時,限制 T 的類型
// 只有實現了 Debug trait 的類型才能使用這個方法
// 這就是 trait bound 的作用
// 只為實現了 Debug 的類型實現的方法
//當然,也可以實現我們自定義的trait
impl<T: Debug> Printer<T> {fn print(&self) {println!("{:?}", self.0);}
}fn main() {let p1 = Printer::new(42); // i32 實現了 Debugp1.print(); // 可以調用 printlet p2 = Printer::new(vec![1, 2, 3]); // Vec 實現了 Debugp2.print(); // 可以調用 printstruct MyType; // 未實現 Debuglet p3 = Printer::new(MyType);p3.print(); // 編譯錯誤:MyType 未實現 Debug
}
- 為實現了多個 trait 的類型實現方法
use std::fmt::{ Display, Debug };//元組結構體
// 這個結構體可以存儲任何類型的值
// 只要它們實現了 Display 和 Debug trait
// 這個結構體的作用是將值打印出來
// 這個結構體的泛型參數 T 可以是任何類型
struct MultiPrinter<T>(T);impl<T> MultiPrinter<T> {fn new(value: T) -> Self {MultiPrinter(value)}
}// 要求同時實現 Display 和 Debug
impl<T: Display + Debug> MultiPrinter<T> {fn print_all(&self) {println!("Display: {}", self.0);println!("Debug: {:?}", self.0);}
}fn main() {let printer = MultiPrinter::new("Hello, world!");printer.print_all();let printer2 = MultiPrinter::new(42);printer2.print_all();
}
🛠? 4)有條件地實現trait
可以對任何實現了特定 trait 的類型有條件的實現 trait。
對任何滿足特定 trait bound 的類型實現 trait 被稱為 blanket implementations,他們被廣泛的用于 Rust 標準庫中。
類似于其他語言的繼承
例如,標準庫為任何實現了 Display trait 的類型實現了 ToString trait。這個 impl 塊看起來像這樣:
impl<T: Display> ToString for T {// --snip--
}
因為標準庫有了這些 blanket implementation,我們可以對任何實現了 Display trait 的類型調用由 ToString 定義的to_string 方法。
例如,可以將整型轉換為對應的 String 值,因為整型實現了 Display :
let s = 3.to_string();
blanket implementation 會出現在 trait 文檔的 “Implementers” 部分。
trait 和 trait bound 讓我們使用泛型類型參數來減少重復,并仍然能夠向編譯器明確指定泛型類型需要擁有哪些行為。
因為我們向編譯器提供了 trait bound 信息,它就可以檢查代碼中所用到的具體類型是否提供了正確的行為。
在動態類型語言中,如果我們嘗試調用一個類型并沒有實現的方法,會在運行時出現錯誤。
Rust 將這些錯誤移動到了編譯時,甚至在代碼能夠運行之前就強迫我們修復錯誤。
另外,我們也無需編寫運行時檢查行為的代碼,因為在編譯時就已經檢查過了,這樣相比其他那些不愿放棄泛型靈活性的語言有更好的性能。
基本語法
impl<T: TraitBound> MyTrait for T {// 實現方法
}
或者使用 where 子句:
impl<T> MyTrait for T
whereT: Trait1 + Trait2,
{// 實現方法
}
常見應用模式
- 為實現了其他 trait 的類型實現你的 trait
use std::fmt::Display;trait Printable {fn print(&self);
}// 為所有實現了 Display 的類型自動實現 Printable
impl<T: Display> Printable for T {fn print(&self) {println!("{}", self);}
}fn main() {(42).print(); // 可以調用,因為 i32 實現了 Display"hello".print(); // 可以調用,因為 &str 實現了 Display
}
只要實現了Display的類型,都會自動實現我們自定義的trait Printable
其實例就可以調用print方法
- 為特定類型組合實現 trait
trait Greet {fn greet(&self);
}struct Person;
struct Dog;// 只為 Person 實現 Greet
impl Greet for Person {fn greet(&self) {println!("Hello!");}
}// 有條件地為某些泛型類型實現 Greet
// 實現Greet的T,Vec<T>也實現了Greet
impl<T> Greet for Vec<T> where T: Greet {fn greet(&self) {for item in self {item.greet();}}
}fn main() {let person = Person;person.greet(); // 正常調用let people = vec![Person, Person];people.greet(); // 調用 Vec 的實現// let dogs = vec![Dog, Dog];// dogs.greet(); // 編譯錯誤,因為 Dog 沒有實現 Greet
}
🧠 6)使用 trait bound 的好處
類型安全:編譯時就能檢查類型是否滿足要求。
泛型復用:編寫更通用的代碼。
自動推導實現:結合 derive 宏可以快速添加常用 trait(如 Debug, Clone 等)。
4. trait作為返回值
在 Rust 中,trait 不能直接作為函數返回值的類型,因為 trait 是一個抽象類型(即它本身沒有大小,Sized)。
但我們可以通過兩種方式讓函數 “返回一個實現了 trait 的值”:
1)使用 impl Trait 作為返回類型(靜態分發 ?)
impl Trait 作為返回類型時,函數必須返回單一的具體類型,不能根據條件返回不同類型。
返回impl Trait,其實就是返回實現了這個特征的類型對象
//trait作為返回值
// trait 作為返回值
// trait 作為返回值是 Rust 中的一種用法,可以讓函數返回實現了某個 trait 的類型
// trait 作為返回值的語法有兩種:
//一種是靜態發布,返回特征的實現 fn function_name() -> impl Trait
//一種是動態發布,fn function_name() -> Box<dyn Trait>,其中 Box<dyn Trait> 是一個 trait 對象
// trait 對象是一個指向實現了 trait 的類型的指針
// trait 對象可以在運行時動態地決定具體的類型trait Animal {fn speak(&self) -> String;
}struct Dog;impl Animal for Dog {fn speak(&self) -> String {"Woof!".to_string()}
}// 返回一個實現了 Animal 的具體類型(靜態分發)
//返回值是個特征的實現的時候,就是返回實現了這個特征的對象
fn get_animal() -> impl Animal {Dog
}fn main() {let animal = get_animal();println!("{}", animal.speak());
}
2)使用 Box 返回 trait 對象(動態分發 ?)
使用 trait 對象 (dyn Trait) 返回多種類型
//trait作為返回值
// trait 作為返回值
// trait 作為返回值是 Rust 中的一種用法,可以讓函數返回實現了某個 trait 的類型
// trait 作為返回值的語法是:fn function_name() -> Box<dyn Trait>,其中 Box<dyn Trait> 是一個 trait 對象
// trait 對象是一個指向實現了 trait 的類型的指針
// trait 對象可以在運行時動態地決定具體的類型
// trait 對象的大小是固定的,可以在運行時動態地決定具體的類型trait Animal {fn speak(&self) -> String;
}struct Dog;
struct Cat;impl Animal for Cat {fn speak(&self) -> String {"Meow!".to_string()}
}impl Animal for Dog {fn speak(&self) -> String {"Woof!".to_string()}
}//動態發布
fn dyget_animal(choice: u8) -> Box<dyn Animal> {if choice == 0 { Box::new(Dog) } else { Box::new(Cat) }
}fn main() {let animal = dyget_animal(0);println!("{}", animal.speak());let animal = dyget_animal(1);println!("{}", animal.speak());}
這是 trait 作為返回值的“對象安全”用法。
? 優點:
可以返回不同的具體類型(如 Dog 或 Cat)
靈活性更高
?? 限制:
動態分發,運行時有一點性能開銷
需要 dyn Trait 是對象安全(只能包含不依賴于 Self 的方法,且不能有泛型)
注意事項
對象安全:當使用 dyn Trait 時,trait 必須是對象安全的
不能有返回 Self 的方法
不能有泛型方法
生命周期:trait 對象默認有 'static 生命周期,如果需要更短的生命周期需要明確指定
兩種方法比較
5. 注意事項
孤兒規則:實現 trait 時,必須保證 trait 或類型至少有一個是在當前 crate 中定義的
特化限制:Rust 目前不支持完全的 trait 實現特化
方法優先級:更具體的實現會覆蓋更通用的實現
沖突實現:避免創建會導致編譯器無法確定使用哪個實現的場景
文檔:為條件實現添加清晰的文檔說明
3. 標準庫中的常用Trait
3.1 格式化相關Trait
Display:用戶友好的展示
Debug:調試輸出
LowerHex:十六進制小寫格式化
use std::fmt;struct Point {x: i32,y: i32,
}//讓自己實現的類型實現 Display 和 Debug trait
// 通過實現 fmt::Display trait 來實現格式化輸出
// 通過實現 fmt::Debug trait 來實現調試輸出
impl fmt::Display for Point {fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {write!(f, "({}, {})", self.x, self.y)}
}impl fmt::Debug for Point {fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {f.debug_struct("Point").field("x", &self.x).field("y", &self.y).finish()}
}fn main() {let p = Point { x: 1, y: 2 };println!("Display: {}", p);println!("Debug: {:?}", p);
}
3.2 轉換Trait
From/Into:類型轉換
TryFrom/TryInto:可能失敗的轉換
AsRef/AsMut:引用轉換
struct Inches(f64);
struct Millimeters(f64);impl From<Millimeters> for Inches {fn from(mm: Millimeters) -> Self {Inches(mm.0 / 25.4)}
}fn print_inches(inches: Inches) {println!("{} inches", inches.0);
}fn main() {let mm = Millimeters(254.0);let inches: Inches = mm.into();print_inches(inches); // 輸出: 10 inches
}
3.3 運算符重載Trait
Add/Sub/Mul/Div:算術運算
Neg:一元負號
Index/IndexMut:索引操作
use std::ops::{ Add, Mul };
struct Vector {x: f64,y: f64,
}impl Add for Vector {type Output = Vector;fn add(self, other: Vector) -> Vector {Vector {x: self.x + other.x,y: self.y + other.y,}}
}impl Mul<f64> for Vector {type Output = Vector;fn mul(self, scalar: f64) -> Vector {Vector {x: self.x * scalar,y: self.y * scalar,}}
}fn main() {let v1 = Vector { x: 1.0, y: 2.0 };let v2 = Vector { x: 3.0, y: 4.0 };let v3 = v1 + v2;println!("v3: ({}, {})", v3.x, v3.y);let scalar = 2.0;let v4 = v3 * scalar;println!("v4: ({}, {})", v4.x, v4.y);
}
3.4 其他比較重要的內置trait
Clone: 顯式復制對象
Copy: 標記類型可以在賦值時進行位復制
PartialEq/Eq: 相等比較
PartialOrd/Ord: 排序比較
Default: 創建默認值
Iterator: 迭代器
Drop: 自定義析構邏輯