# 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.参考文献
- 附录:名词中英文对照