# Traits
> [traits.md](https://github.com/rust-lang/rust/blob/master/src/doc/book/traits.md)
commit 6ba952020fbc91bad64be1ea0650bfba52e6aab4
trait 是一個告訴 Rust 編譯器一個類型必須提供哪些功能語言特性。
你還記得`impl`關鍵字嗎,曾用[方法語法](#)調用方法的那個?
~~~
struct Circle {
x: f64,
y: f64,
radius: f64,
}
impl Circle {
fn area(&self) -> f64 {
std::f64::consts::PI * (self.radius * self.radius)
}
}
~~~
trait 也很類似,除了我們用函數標記來定義一個 trait,然后為結構體實現 trait。例如,我們為`Circle`實現`HasArea` trait:
~~~
struct Circle {
x: f64,
y: f64,
radius: f64,
}
trait HasArea {
fn area(&self) -> f64;
}
impl HasArea for Circle {
fn area(&self) -> f64 {
std::f64::consts::PI * (self.radius * self.radius)
}
}
~~~
如你所見,`trait`塊與`impl`看起來很像,不過我們沒有定義一個函數體,只是函數標記。當我們`impl`一個trait時,我們使用`impl Trait for Item`,而不是僅僅`impl Item`。
### 泛型函數的 trait bound(Trait bounds on generic functions)
trait 很有用是因為他們允許一個類型對它的行為提供特定的承諾。泛型函數可以顯式的限制(或者叫 [bound](#))它接受的類型。考慮這個函數,它并不能編譯:
~~~
fn print_area<T>(shape: T) {
println!("This shape has an area of {}", shape.area());
}
~~~
Rust抱怨道:
~~~
error: no method named `area` found for type `T` in the current scope
~~~
因為`T`可以是任何類型,我們不能確定它實現了`area`方法。不過我們可以在泛型`T`添加一個 trait bound,來確保它實現了對應方法:
~~~
# trait HasArea {
# fn area(&self) -> f64;
# }
fn print_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 {
fn area(&self) -> f64;
}
struct Circle {
x: f64,
y: f64,
radius: f64,
}
impl HasArea for Circle {
fn area(&self) -> f64 {
std::f64::consts::PI * (self.radius * self.radius)
}
}
struct Square {
x: f64,
y: f64,
side: f64,
}
impl HasArea for Square {
fn area(&self) -> f64 {
self.side * self.side
}
}
fn print_area<T: HasArea>(shape: T) {
println!("This shape has an area of {}", shape.area());
}
fn main() {
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.141593
This shape has an area of 1
~~~
如你所見,`print_area`現在是泛型的了,并且確保我們傳遞了正確的類型。如果我們傳遞了錯誤的類型:
~~~
print_area(5);
~~~
我們會得到一個編譯時錯誤:
~~~
error: the trait `HasArea` is not implemented for the type `_` [E0277]
~~~
### 泛型結構體的 trait bound(Trait bounds on generic structs)
泛型結構體也從 trait bound 中獲益。所有你需要做的就是在你聲明類型參數時附加上 bound。這里有一個新類型`Rectangle<T>`和它的操作`is_square()`:
~~~
struct Rectangle<T> {
x: T,
y: T,
width: T,
height: T,
}
impl<T: PartialEq> Rectangle<T> {
fn is_square(&self) -> bool {
self.width == self.height
}
}
fn main() {
let mut r = Rectangle {
x: 0,
y: 0,
width: 47,
height: 47,
};
assert!(r.is_square());
r.height = 42;
assert!(!r.is_square());
}
~~~
`is_square()`需要檢查邊是相等的,所以邊必須是一個實現了[`core::cmp::PartialEq`](http://doc.rust-lang.org/cmp/trait.PartialEq.html) trait 的類型:
~~~
impl<T: PartialEq> Rectangle<T> { ... }
~~~
現在,一個長方形可以用任何可以比較相等的類型定義了。
這里我們定義了一個新的接受任何精度數字的`Rectangle`結構體——講道理,很多類型——只要他們能夠比較大小。我們可以對`HasArea`結構體,`Square`和`Circle`做同樣的事嗎?可以,不過他們需要乘法,而要處理它我們需要了解[運算符 trait](#)更多。
### 實現 trait 的規則(Rules for implementing traits)
目前為止,我們只在結構體上添加 trait 實現,不過你可以為任何類型實現一個 trait。所以從技術上講,你可以在`i32`上實現`HasArea`:
~~~
trait HasArea {
fn area(&self) -> f64;
}
impl HasArea for i32 {
fn area(&self) -> f64 {
println!("this is silly");
*self as f64
}
}
5.area();
~~~
在基本類型上實現方法被認為是不好的設計,即便這是可以的。
這看起來有點像狂野西部(Wild West),不過這還有兩個限制來避免情況失去控制。第一是如果 trait 并不定義在你的作用域,它并不能實現。這是個例子:為了進行文件I/O,標準庫提供了一個[`Write`](http://doc.rust-lang.org/nightly/std/io/trait.Write.html)trait來為`File`增加額外的功能。默認,`File`并不會有這個方法:
~~~
let mut f = std::fs::File::open("foo.txt").ok().expect("Couldn’t open foo.txt");
let buf = b"whatever"; // byte string literal. buf: &[u8; 8]
let result = f.write(buf);
# result.unwrap(); // ignore the error
~~~
這里是錯誤:
~~~
error: type `std::fs::File` does not implement any method in scope named `write`
let result = f.write(buf);
^~~~~~~~~~
~~~
我們需要先`use`這個`Write` trait:
~~~
use std::io::Write;
let mut f = std::fs::File::open("foo.txt").expect("Couldn’t open foo.txt");
let buf = b"whatever";
let result = f.write(buf);
# result.unwrap(); // ignore the error
~~~
這樣就能無錯誤的編譯了。
這意味著即使有人做了像給`int`增加函數這樣的壞事,它也不會影響你,除非你`use`了那個trait。
這還有一個實現trait的限制。不管是trait還是你寫的`impl`都只能在你自己的包裝箱內生效。所以,我們可以為`i32`實現`HasArea`trait,因為`HasArea`在我們的包裝箱中。不過如果我們想為`i32`實現`Float`trait,它是由Rust提供的,則無法做到,因為這個trait和類型都不在我們的包裝箱中。
關于trait的最后一點:帶有trait限制的泛型函數是*單態*(*monomorphization*)(mono:單一,morph:形式)的,所以它是*靜態分發*(*statically dispatched*)的。這是什么意思?查看[trait對象](#)來了解更多細節。
### 多 trait bound(Multiple trait bounds)
你已經見過你可以用一個trait限定一個泛型類型參數:
~~~
fn foo<T: Clone>(x: T) {
x.clone();
}
~~~
如果你需要多于1個限定,可以使用`+`:
~~~
use std::fmt::Debug;
fn foo<T: Clone + Debug>(x: T) {
x.clone();
println!("{:?}", x);
}
~~~
`T`現在需要實現`Clone`和`Debug`。
### where 從句(Where clause)
編寫只有少量泛型和trait的函數并不算太糟,不過當它們的數量增加,這個語法就看起來比較詭異了:
~~~
use std::fmt::Debug;
fn foo<T: Clone, K: Clone + Debug>(x: T, y: K) {
x.clone();
y.clone();
println!("{:?}", y);
}
~~~
函數的名字在最左邊,而參數列表在最右邊。限制寫在中間。
Rust有一個解決方案,它叫“where 從句”:
~~~
use std::fmt::Debug;
fn foo<T: Clone, K: Clone + Debug>(x: T, y: K) {
x.clone();
y.clone();
println!("{:?}", y);
}
fn bar<T, K>(x: T, y: K) where T: Clone, K: Clone + Debug {
x.clone();
y.clone();
println!("{:?}", y);
}
fn main() {
foo("Hello", "world");
bar("Hello", "world");
}
~~~
`foo()`使用我們剛才的語法,而`bar()`使用`where`從句。所有你所需要做的就是在定義參數時省略限制,然后在參數列表后加上一個`where`。對于很長的列表,你也可以加上空格:
~~~
use std::fmt::Debug;
fn bar<T, K>(x: T, y: K)
where T: Clone,
K: Clone + Debug {
x.clone();
y.clone();
println!("{:?}", y);
}
~~~
這種靈活性可以使復雜情況變得簡潔。
`where`也比基本語法更強大。例如:
~~~
trait ConvertTo<Output> {
fn convert(&self) -> Output;
}
impl ConvertTo<i64> for i32 {
fn convert(&self) -> i64 { *self as i64 }
}
// can be called with T == i32
fn normal<T: ConvertTo<i64>>(x: &T) -> i64 {
x.convert()
}
// can be called with T == i64
fn inverse<T>() -> T
// this is using ConvertTo as if it were "ConvertTo<i64>"
where i32: ConvertTo<T> {
42.convert()
}
~~~
這突顯出了`where`從句的額外的功能:它允許限制的左側可以是任意類型(在這里是`i32`),而不僅僅是一個類型參數(比如`T`)。
### 默認方法(Default methods)
關于trait還有最后一個我們需要講到的功能。它簡單到只需我們展示一個例子:
~~~
trait Foo {
fn is_valid(&self) -> bool;
fn is_invalid(&self) -> bool { !self.is_valid() }
}
~~~
`Foo`trait的實現者需要實現`is_valid()`,不過并不需要實現`is_invalid()`。它會使用默認的行為。你也可以選擇覆蓋默認行為:
~~~
# trait Foo {
# fn is_valid(&self) -> bool;
#
# fn is_invalid(&self) -> bool { !self.is_valid() }
# }
struct UseDefault;
impl Foo for UseDefault {
fn is_valid(&self) -> bool {
println!("Called UseDefault.is_valid.");
true
}
}
struct OverrideDefault;
impl Foo for OverrideDefault {
fn is_valid(&self) -> bool {
println!("Called OverrideDefault.is_valid.");
true
}
fn is_invalid(&self) -> bool {
println!("Called OverrideDefault.is_invalid!");
true // overrides the expected value of is_invalid()
}
}
let default = UseDefault;
assert!(!default.is_invalid()); // prints "Called UseDefault.is_valid."
let over = OverrideDefault;
assert!(over.is_invalid()); // prints "Called OverrideDefault.is_invalid!"
~~~
### 繼承(Inheritance)
有時,實現一個trait要求實現另一個trait:
~~~
trait Foo {
fn foo(&self);
}
trait FooBar : Foo {
fn foobar(&self);
}
~~~
`FooBar`的實現也必須實現`Foo`,像這樣:
~~~
# trait Foo {
# fn foo(&self);
# }
# trait FooBar : Foo {
# fn foobar(&self);
# }
struct Baz;
impl Foo for Baz {
fn foo(&self) { println!("foo"); }
}
impl FooBar for Baz {
fn foobar(&self) { println!("foobar"); }
}
~~~
如果我們忘了實現`Foo`,Rust會告訴我們:
~~~
error: the trait `main::Foo` is not implemented for the type `main::Baz` [E0277]
~~~
### Deriving
重復的實現像`Debug`和`Default`這樣的 trait 會變得很無趣。為此,Rust 提供了一個[屬性](#)來允許我們讓 Rust 為我們自動實現 trait:
~~~
#[derive(Debug)]
struct Foo;
fn main() {
println!("{:?}", Foo);
}
~~~
然而,deriving 限制為一些特定的 trait:
- [Clone](http://doc.rust-lang.org/core/clone/trait.Clone.html)
- [Copy](http://doc.rust-lang.org/core/marker/trait.Copy.html)
- [Debug](http://doc.rust-lang.org/core/fmt/trait.Debug.html)
- [Default](http://doc.rust-lang.org/core/default/trait.Default.html)
- [Eq](http://doc.rust-lang.org/core/cmp/trait.Eq.html)
- [Hash](http://doc.rust-lang.org/core/hash/trait.Hash.html)
- [Ord](http://doc.rust-lang.org/core/cmp/trait.Ord.html)
- [PartialEq](http://doc.rust-lang.org/core/cmp/trait.PartialEq.html)
- [PartialOrd](http://doc.rust-lang.org/core/cmp/trait.PartialOrd.html)
- 前言
- 貢獻者
- 1.介紹
- 2.準備
- 3.學習 Rust
- 3.1.猜猜看
- 3.2.哲學家就餐問題
- 3.3.其它語言中的 Rust
- 4.語法和語義
- 4.1.變量綁定
- 4.2.函數
- 4.3.原生類型
- 4.4.注釋
- 4.5.If語句
- 4.6.循環
- 4.7.所有權
- 4.8.引用和借用
- 4.9.生命周期
- 4.10.可變性
- 4.11.結構體
- 4.12.枚舉
- 4.13.匹配
- 4.14.模式
- 4.15.方法語法
- 4.16.Vectors
- 4.17.字符串
- 4.18.泛型
- 4.19.Traits
- 4.20.Drop
- 4.21.if let
- 4.22.trait 對象
- 4.23.閉包
- 4.24.通用函數調用語法
- 4.25.crate 和模塊
- 4.26.const和static
- 4.27.屬性
- 4.28.type別名
- 4.29.類型轉換
- 4.30.關聯類型
- 4.31.不定長類型
- 4.32.運算符和重載
- 4.33.Deref強制多態
- 4.34.宏
- 4.35.裸指針
- 4.36.不安全代碼
- 5.高效 Rust
- 5.1.棧和堆
- 5.2.測試
- 5.3.條件編譯
- 5.4.文檔
- 5.5.迭代器
- 5.6.并發
- 5.7.錯誤處理
- 5.8.選擇你的保證
- 5.9.外部函數接口
- 5.10.Borrow 和 AsRef
- 5.11.發布途徑
- 5.12.不使用標準庫
- 6.Rust 開發版
- 6.1.編譯器插件
- 6.2.內聯匯編
- 6.4.固有功能
- 6.5.語言項
- 6.6.鏈接進階
- 6.7.基準測試
- 6.8.裝箱語法和模式
- 6.9.切片模式
- 6.10.關聯常量
- 6.11.自定義內存分配器
- 7.詞匯表
- 8.語法索引
- 9.參考文獻
- 附錄:名詞中英文對照