Trait 本意是特性,特質,特徵等等,其實主要指人的性格特徵。不明白為什麼rust的創造者不使用feature這樣單詞。
如作者所言:
Note: Traits are similar to a feature often called interfaces in other languages, although with some differences.
特徵類似於其它語言的介面,但和介面還是有一些區別的。
為了便於行文,本文把Trait翻譯為特質。
我查了一些資料,可以確認這句話基本上是對的。不對的在哪裡了?此處先不聊了。 總之特質在大部分情況下可以當做介面即可。
但是不能把特質稱為介面,因為介面僅僅是特質的一個功能,它還有其它作用。
本文的內容主要都是為了通用型別服務,前面講了很多介面特質的內容。
從本章開始,可以看到越來越多奇奇怪怪的語法,雖然我已經學過不少語言,但Rust絕對是其中的奇葩!
一、如何定義一個介面特質
所謂介面特質,即作為介面使用的特質。
就一個步驟:
pub trait Work{ fn design(&self); fn code(&self); fn test(&self); }
1.pub修飾符可選
2.一個介面特質中可以定義多個方法
這和大部分語言差不多。
二、為結構體實現一個介面特質
2.1基本實現
實現一個介面特質也很簡單,利用impl語法:
幾個注意事項:
1.當實現一個介面特質的時候,必須實現這個介面特質中的所有方法,不能只有一部分
2.不能越界實現其它單元包中的介面特質。例如在單元包a存在介面特質 Ta,那麼無法在單元包b中實現Ta
如果違反了,會提示:only traits defined in the current crate can be implemented for types defined outside of the crate
define and implement a trait or new type instead
3.如果介面特質T在當前單元包,那麼無論物件(結構體、列舉等)O位於哪裡,那麼都可以為O實現T
4.在同一個單元包內,你不能在不同模組為同個物件實現多次介面特質
以上第2條並不是普適的。rust的一些特質是位於標準庫中,但允許你在自己的模組中實現這些介面特質,典型的是Display
2.2 預設實現
和java一樣(從J8開始),rust也提供了預設實現,只不過java把介面搞得更加複雜一些。
trait Summary { fn summarize(&self) -> String; fn get_content(&self) -> &String; fn is_empty(&self) -> bool { self.get_content().is_empty() } }
方法is_empty就是預設的實現。這樣在實現程式碼中,不需要提供is_empty有關的程式碼,也可以正常使用:
trait Summary { fn summarize(&self) -> String; fn get_content(&self) -> &String; fn is_empty(&self) -> bool { self.get_content().is_empty() } } struct NewsArticle { pub headline: String, pub location: String, pub author: String, pub content: String } impl Summary for NewsArticle { fn summarize(&self) -> String { format!("{}, by {} ({}) \n {}", self.headline, self.author, self.location,self.content) } fn get_content(&self) -> &String { &self.content } } fn main(){ let news= NewsArticle { headline: String::from("英雄紀念碑"), location: String::from("中國北京"), author: String::from("新華社記者m--澤--東"), content: String::from("由此上溯到一千八百四十年,從那時起,為了反對內外敵人, 爭取民族獨立和人民自由幸福,在歷次鬥爭中犧牲的人民英雄們永垂不朽!") }; println!("{}", news.summarize()); println!("{}", news.is_empty()); }
預設方法是否可以覆蓋了?可以的,這個和java也一樣:
impl Summary for NewsArticle { fn summarize(&self) -> String { format!("{}, by {} ({}) \n {}", self.headline, self.author, self.location,self.content) } fn get_content(&self) -> &String { &self.content } fn is_empty(&self) -> bool { println!("{}", self.content.len()); self.content.len()<1000 } }
預設實現,減少了打碼量,尤其是這個介面特質可能被許多物件實現的時候。
三、介面特質作為一個引數
這裡討論幾個問題:
- 如何把一個介面特質作為一個引數,或者說程式碼上如何寫?語法是什麼
- 如果要求所有的引數實現同個介面特質,且是同種型別,要怎麼寫?
- 如果引數要求實現多個介面特質怎麼定義。即某個引數P需要實現介面特質T1,T2,..Tn,那麼如何書寫(非如何為P實現特質介面T1,T2..Tn)?
3.1 定義-簡化形式
語法如下: fn xxx( p: impl ***)
其中xxx是方法名,p是變數名,***是特質名
fn print(article:&impl Summary){ println!("{}", article.get_content()); }
語法比較怪異,這都rust自己挖坑,自己跳。不過比起一些故意折騰人的語言,也還可以將就接受。 也就是因為這個,rustc被設計的特別強大,編譯資訊也特別貼心,否則這些奇奇怪怪的不容易記憶。
如果是java等比較人性,也簡單: 例如 public get(IBook:book)
如果沒有什麼特別要求,那麼就這樣寫吧!
3.2 定義-正規形式,及其特有作用
書上說,impl 語法是一個語法糖(我認為不是太合適。真要那麼想,基本上所有的都是語法糖,只能說有多種形式),正規的寫法是:fn xxxx<T:t>(p:t)
就是在方法後直接跟上<T:t>這樣的形式。
例如上面那個print,可以寫成如下:
/** * 使用<T:t>的方式,是impl trait的方式的正規形式。 * 即print_normal是print的正規形式 */ fn print_normal<T:Summary>(article:&T){ println!("使用<T:t>的方式:{}", article.get_content()); }
這種正規形式的另外一個用處:如果方法帶有多個實現了指定介面特質的引數,同時要求這幾個引數都是同個型別,那麼必須使用正規格式。
來個例子:
例子1- 報錯的例子
struct Box{vol: u32} struct tube{x:u32,y:u32, z:u32,vol: u32} trait Brush{fn draw(&self);} impl Brush for Box{ fn draw(&self){ println!("draw box"); } } impl Brush for tube{ fn draw(&self){ println!("draw tube: x:{},y:{},z:{}"); } } fn draw_objet(b1:&impl Brush, b2:&impl Brush){b1.draw();b2.draw();} fn draw_objet2<T:Brush>(b1:&T, b2:&T){b1.draw();b2.draw();} fn main() { let b1 = Box{vol: 20}; let t1 = tube{x:5, y:3, z:4, vol: 10}; draw_objet(&b1,&t1); draw_objet2(&b1,&t1); }
執行後,編譯報錯:
提示的很清楚了,這是因為 draw_objet2要求兩個引數一致,但現在不一致,一個是&Box,一個是&Cube
但是draw_object沒有這個問題,它使用的是簡單形式。
3.3 如何限定一個引數必須實現多個介面特質
方式有2個:
- 使用+號,連線多個介面特質
- 使用where字句
示例
use std::fmt::Display; use std::fmt; struct Box{vol: u32} struct tube{x:u32,y:u32, z:u32,vol: u32} trait Brush{fn draw(&self);} impl Brush for Box{ fn draw(&self){ println!("draw box"); } } impl Brush for tube{ fn draw(&self){ println!("{}",self); } } impl fmt::Display for tube{ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f,"x:{},y:{},z:{}.體積={}",self.x,self.y,self.z,self.vol) } } fn draw_objet(b1:&impl Brush, b2:&impl Brush){b1.draw();b2.draw();} fn draw_objet2<T:Brush>(b1:&T, b2:&T){b1.draw();b2.draw();} //+的第一個形式:在引數中書寫 fn draw_ojbect3(shape:&(impl Brush+Display)){ shape.draw(); } //+的第二個形式:在方法名後書寫 fn draw_object4<T:Brush+Display>(shape:&T){ shape.draw(); } //+的第三個形式:使用where字句 fn draw_ojbect31<T>(shape:&T) where T:Display+Brush { shape.draw(); } fn main() { let b1 = Box{vol: 20}; let t1 = tube{x:5, y:3, z:4, vol: 60}; let t2= tube{x:10,y:8,z:3,vol:210}; draw_objet(&b1,&t1); //draw_objet2(&b1,&t1);//這樣會報錯的,因為draw_objet2函式要求引數型別必須一致 draw_objet2(&t1,&t2); // //draw_ojbect3(&b1); //這個明顯是錯誤的,因為要求,因為drawo_ojbect3要求引數必須實現兩個介面特質 draw_ojbect3(&t1); draw_object4(&t1); draw_ojbect31(&t2); }
四、返回介面特質
和其它語言類似,但是這個有個問題:rust編譯器只把返回的當作介面特質,而不是當作具體的物件(結構體或者列舉)。
所以,如果沒有類似其它語言的強制轉換,或者編譯器支援,企圖把返回的介面特質當作某個物件,那是不行的。
fn create_shape(px:u32,py:u32,pz:u32)->impl Brush{ Tube{ x:px, y:py, z:pz, vol:px*py*pz } } let my_tube=create_shape(10,20,40);
//println!("{}",my_tube.vol); // 這樣會報錯,因為編譯器無法指導my_tube具體型別 my_tube.draw(); //但這個可以的。 所以能不能知道,全看編譯器或者是rust發明人的意願了。
上例中,為什麼my_tube.vol會報告異常,是因為rust編譯器的邏輯只認為my_tube是一個介面特質,所以my_tube不能呼叫Tube的屬性/方法,但是
my_tube可以呼叫Brush的具體方法。
五、幫助實現通用型別函式/方法
前面的一堆內容就是為了兩個目的:如何定義和實現介面特質;如何在通用型別方法中限定引數的範圍。
“如何定義和實現”基本上都明白了,現在就示例下如何實現“通用型別引數限定範圍”。
方式是:利用介面特質
語法:<P:T> 或者<P:T1+T2+..Tn>,或者也可以使用where字句
fn largest<T: std::cmp::PartialOrd>(list: &[T]) -> &T { let mut largest = &list[0]; for item in list { if item > largest { largest = item; } } largest }
六、小結
- 介面特質是特質的一個部分,不能把特質翻譯為介面
- 定義介面特質還是比較簡單的。可以在一個介面特質中定義多個方法。
- 不能在當前單元包中實現在其它單元包中定義的介面特質。但是存在例外情況,例如一些rust位於標準庫中的介面特質;一個單元包內,不能在不同模組為一個介面特質,一個物件做n次實現
- 介面特質的方法可以有預設實現;預設實現的方法可以被覆蓋
- 在方法中可以使用介面特質作為引數。有兩種方式:impl,標準。前者不會強制所有引數同個型別,後者會
- 還可以使用where字句來限定引數的介面特質。
- 某個引數如果要繫結(限定)多個介面,可以使用+符號
- 介面特質的出現,使得定義引數,方法變得更加靈活,也更加複雜
- 函式/方法可以返回介面特質,但如果沒有特別措施,不能把返回的結果當作具體型別使用。編譯器只會把返回結果當值介面特質,即使你在方法體中明確返回的型別。