# 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.学院派研究
- 勘误