# Traits
你還記得`impl`關鍵字嗎,曾用[方法語法](http://doc.rust-lang.org/nightly/book/method-syntax.html)調用方法的那個?
~~~
struct Circle { x: f64, y: f64, radius: f64,}impl Circle { fnarea(&self) -> f64 { std::f64::consts::PI * (self.radius * self.radius) }}
~~~
trait也很類似,除了我們用函數標記來定義一個trait,然后為結構體實現trait。例如:
~~~
struct Circle { x: f64, y: f64, radius: f64,}trait HasArea { fnarea(&self) -> f64;}impl HasArea for Circle { fnarea(&self) -> f64 { std::f64::consts::PI * (self.radius * self.radius) }}
~~~
如你所見,`trait`塊與`impl`看起來很像,不過我們沒有定義一個函數體,只是函數標記。當我們`impl`一個trait時,我們使用`impl Trait for Item`,而不是僅僅`impl Item`。
那么這有什么重要的呢?還記得我們使用泛型`inverse`函數得到的錯誤嗎?
~~~
error: binary operation `==` cannot be applied to type `T`
~~~
我們可以用trait來約束我們的泛型。考慮下這個函數,它不能編譯并給出一個類似的錯誤:
~~~
fnprint_area<T>(shape: T) { println!("This shape has an area of {}", shape.area());}
~~~
Rust抱怨說:
~~~
fn print_area<T>(shape: T) { println!("This shape has an area of {}", shape.area());}
~~~
因為`T`可以是任何類型,我們不能確定它實現了`area`方法。不過我們可以在泛型`T`添加一個_trait約束_(_trait constraint_),來確保它實現了對應方法:
~~~
fnprint_area<T: HasArea>(shape: T) { println!("This shape has an area of {}", shape.area());}
~~~
`<T: HasArea>`語法是指`any type that implements the HasArea trait`(任何實現了`HasArea`trait的類型)。因為trait定義了函數類型標記,我們可以確定任何實現`HasArea`將會擁有一個`.area()`方法。
這是一個擴展的例子演示它如何工作:
~~~
trait HasArea { fnarea(&self) -> f64;}struct Circle { x: f64, y: f64, radius: f64,}impl HasArea for Circle { fnarea(&self) -> f64 { std::f64::consts::PI * (self.radius * self.radius) }}struct Square { x: f64, y: f64, side: f64,}impl HasArea for Square { fnarea(&self) -> f64 { self.side * self.side }}fnprint_area<T: HasArea>(shape: T) { println!("This shape has an area of {}", shape.area());}fnmain() { let c = Circle { x: 0.0f64, y: 0.0f64, radius: 1.0f64, }; let s = Square { x: 0.0f64, y: 0.0f64, side: 1.0f64, }; print_area(c); print_area(s);}
~~~
這個程序會輸出:
~~~
This shape has an area of 3.141593This shape has an area of 1
~~~
如你所見,`print_area`現在是泛型的了,并且確保我們傳遞了正確的類型。如果我們傳遞了錯誤的類型:
~~~
print_area(5);
~~~
我們會得到一個編譯時錯誤:
~~~
error: failed to find an implementation of trait main::HasArea for int
~~~
目前為止,我們只在結構體上添加trait實現,不過你為任何類型實現一個trait。所以技術上講,你可以在`i32`上實現`HasArea`:
~~~
trait HasArea { fnarea(&self) -> f64;}impl HasArea for i32 { fnarea(&self) -> f64 { println!("this is silly"); *self as f64 }}5.area();
~~~
在基本類型上實現方法被認為是不好的設計,即便這是可以的。
這看起來有點像狂野西部(Wild West),不過這還有兩個限制來避免情況失去控制。第一是如果trait并不定義在你的作用域,它并不能實現。這是個例子:標準庫提供了一個[`Write`](http://doc.rust-lang.org/nightly/std/io/trait.Write.html)trait來為`File`增加額外的功能,為了進行文件I/O。默認,`File`并不會有這個方法:
~~~
let mut f = std::fs::File::open("foo.txt").ok().expect("Couldn’t open foo.txt");let result = f.write("whatever".as_bytes());
~~~
這里是錯誤:
~~~
error: type `std::fs::File` does not implement any method in scope named `write`let result = f.write(b”whatever”); ^~~~~~~~~~~~~~~~~~
~~~
我們需要先`use``Write`trait:
~~~
use std::io::Write;let mut f = std::fs::File::open("foo.txt").ok().expect("Couldn’t open foo.txt");let result = f.write("whatever".as_bytes());
~~~
這樣就能無錯誤的編譯了。
這意味著即使有人做了像給`int`增加函數這樣的壞事,它也不會影響你,除非你`use`了那個trait。
這還有一個實現trait的限制。不管是trait還是你寫的`impl`都只能在你自己的包裝箱內生效。所以,我們可以為`i32`實現`HasArea`trait,因為`HasArea`在我們的包裝箱中。不過如果我們想為`i32`實現`Float`trait,它是由Rust提供的,則無法做到,因為這個trait和類型都不在我們的包裝箱中。
關于trait的最后一點:帶有trait限制的泛型函數是_單態_(_monomorphization_)(mono:單一,morph:形式)的,所以它是_靜態分發_(_statically dispatched_)的。這是神馬意思?查看[trait對象](http://doc.rust-lang.org/nightly/book/trait-objects.html)來了解更多細節。
### 多trait限定(Multiple trait bounds)
你已經見過你可以用一個trait限定一個泛型類型參數:
~~~
fnfoo<T: Clone>(x: T) { x.clone();}
~~~
如果你需要多于1個限定,以可以使用`+`:
~~~
use std::fmt::Debug;fnfoo<T: Clone + Debug>(x: T) { x.clone(); println!("{:?}", x);}
~~~
`T`現在需要實現`Clone`和`Debug`。
### where從句(Where clause)
編寫只有少量泛型和trait的函數并不算太糟,不過當它們的數量增加,這個語法就看起來比較詭異了:
~~~
use std::fmt::Debug;fnfoo<T: Clone, K: Clone + Debug>(x: T, y: K) { x.clone(); y.clone(); println!("{:?}", y);}
~~~
函數的名字在最左邊,而參數列表在最右邊。限制寫在中間。
Rust有一個解決方案,它叫“where從句”:
~~~
use std::fmt::Debug;fnfoo<T: Clone, K: Clone + Debug>(x: T, y: K) { x.clone(); y.clone(); println!("{:?}", y);}fnbar<T, K>(x: T, y: K) where T: Clone, K: Clone + Debug { x.clone(); y.clone(); println!("{:?}", y);}fnmain() { foo("Hello", "world"); bar("Hello", "workd");}
~~~
`foo()`使用我們剛才的語法,而`bar()`使用`where`從句。所有你所需要做的就是在定義參數時省略限制,然后在參數列表后加上一個`where`。對于很長的列表,你也可以加上空格:
~~~
use std::fmt::Debug;fnbar<T, K>(x: T, y: K) where T: Clone, K: Clone + Debug { x.clone(); y.clone(); println!("{:?}", y);}
~~~
這種靈活性可以使復雜情況變得簡潔。
`where`也比基本語法更強大。例如:
~~~
trait ConvertTo<Output> { fnconvert(&self) -> Output;}impl ConvertTo<i64> for i32 { fnconvert(&self) -> i64 { *self as i64 }}// can be called with T == i32fnnormal<T: ConvertTo<i64>>(x: &T) -> i64 { x.convert()}// can be called with T == i64fninverse<T>() -> T // this is using ConvertTo as if it were "ConvertFrom<i32>" where i32: ConvertTo<T> { 1i32.convert()}
~~~
這突顯出了`where`從句的額外的功能:它允許限制的左側可以是任意類型(在這里是`i32`),而不僅僅是一個類型參數(比如`T`)。
### 默認方法(Default methods)
關于trait還有最后一個我們需要講到的功能。它簡單到只需我們展示一個例子:
~~~
trait Foo { fnbar(&self); fnbaz(&self) { println!("We called baz."); }}
~~~
`Foo`trait的實現者需要實現`bar()`,不過并不需要實現`baz()`。它會使用默認的行為。你也可以選擇覆蓋默認行為:
~~~
struct UseDefault;impl Foo for UseDefault { fnbar(&self) { println!("We called bar."); }}struct OverrideDefault;impl Foo for OverrideDefault { fnbar(&self) { println!("We called bar."); } fnbaz(&self) { println!("Override baz!"); }}let default = UseDefault;default.baz(); // prints "We called bar."let over = OverrideDefault;over.baz(); // prints "Override baz!"
~~~
### 繼承(Inheritance)
有時,實現一個trait要求實現另一個trait:
~~~
trait Foo { fn foo(&self);}trait FooBar : Foo { fn foobar(&self);}
~~~
`FooBar`的實現也必須實現`Foo`,像這樣:
~~~
struct Baz;impl Foo for Baz { fnfoo(&self) { println!("foo"); }}impl FooBar for Baz { fnfoobar(&self) { println!("foobar"); }}
~~~
如果我們忘了實現`Foo`,Rust會告訴我們:
~~~
error: the trait `main::Foo` is not implemented for the type `main::Baz` [E0277]
~~~
- 前言
- 1.介紹
- 2.準備
- 2.1.安裝Rust
- 2.2.Hello, world!
- 2.3.Hello, Cargo!
- 3.學習Rust
- 3.1.猜猜看
- 3.2.哲學家就餐問題
- 3.3.其它語言中的Rust
- 4.高效Rust
- 4.1.棧和堆
- 4.2.測試
- 4.3.條件編譯
- 4.4.文檔
- 4.5.迭代器
- 4.6.并發
- 4.7.錯誤處理
- 4.8.外部語言接口
- 4.9.Borrow 和 AsRef
- 4.10.發布途徑
- 5.語法和語義
- 5.1.變量綁定
- 5.2.函數
- 5.3.原生類型
- 5.4.注釋
- 5.5.If語句
- 5.6.for循環
- 5.7.while循環
- 5.8.所有權
- 5.9.引用和借用
- 5.10.生命周期
- 5.11.可變性
- 5.12.結構體
- 5.13.枚舉
- 5.14.匹配
- 5.15.模式
- 5.16.方法語法
- 5.17.Vectors
- 5.18.字符串
- 5.19.泛型
- 5.20.Traits
- 5.21.Drop
- 5.22.if let
- 5.23.trait對象
- 5.24.閉包
- 5.25.通用函數調用語法
- 5.26.包裝箱和模塊
- 5.27.`const`和`static`
- 5.28.屬性
- 5.29.`type`別名
- 5.30.類型轉換
- 5.31.關聯類型
- 5.32.不定長類型
- 5.33.運算符和重載
- 5.34.`Deref`強制多態
- 5.35.宏
- 5.36.裸指針
- 6.Rust開發版
- 6.1.編譯器插件
- 6.2.內聯匯編
- 6.3.不使用標準庫
- 6.4.固有功能
- 6.5.語言項
- 6.6.鏈接參數
- 6.7.基準測試
- 6.8.裝箱語法和模式
- 6.9.切片模式
- 6.10.關聯常量
- 7.詞匯表
- 8.學院派研究
- 勘誤