Rust:Trait
1、Trait是什么?
一个Trait描述了一种抽象接口(找不到很合适的词),这个抽象接口可以被类型继承。Trait只能由三部分组成(可能只包含部分):
- functions(方法)
- types(类型)
- constants(常量)
所有的Trait都定义了一个隐含类型Self,其指向实现该Trait的类型。Traits可能也包含额外的类型参数,这些类型参数(包括Self),与往常一样可能受到其他Traits等的约束。
类型需要通过独立的implementations去实现不同的Trait。
trait中不必须提供与trait关联的条目的实际定义(类型别名的实际类型、函数的函数体、常数的值表达式),是可选的。如果Trait提供了定义,该定义即为任何实现它的类型的默认行为(如果对应类型没有override的话);如果Trait未提供定义,则任何实现它的类型都必须提供一个定义。
2、Self与self
Self:实现Trait的类型的别名
self:方法参数 fn f(self) {},等价于fn f(self: Self) {}
同理有:
&self等价于self: &Self,
&mut self等价于self: &mut Self
3、默认实现 + 无Override
trait Hello {
fn say_hi(&self) {
println!("hi");
}
}
struct Student {}
impl Hello for Student {}
struct Teacher {}
impl Hello for Teacher {}
fn main() {
let s = Student {};
s.say_hi();
let t = Teacher {};
t.say_hi();
}
默认实现 + Override
trait Hello {
fn say_hi(&self) {
println!("hi");
}
}
struct Nobody {}
impl Hello for Nobody {}
struct Teacher {}
impl Hello for Teacher {
fn say_hi(&self) {
println!("hi, I'm teacher Lee.");
}
}
fn main() {
let n = Nobody {};
n.say_hi();
let t = Teacher {};
t.say_hi();
}
无默认实现
trait Hello {
fn say_hi(&self);
}
struct Student {}
impl Hello for Student {
fn say_hi(&self) {
println!("hi, I'm Jack.");
}
}
struct Teacher {}
impl Hello for Teacher {
fn say_hi(&self) {
println!("hi, I'm teacher Lee.");
}
}
fn main() {
let s = Student {};
s.say_hi();
let t = Teacher {};
t.say_hi();
}
4、孤儿规则(Orphan rule)
如果我们有如下定义,会是什么样呢?
use std::ops::Add;
impl Add<i32> for i32 {
type Output = i32;
fn add(self, other: i32) -> Self::Output {
self - other
}
}
fn main() {}
会发生编译错误,其中一个错误为:
error[E0117]: only traits defined in the current crate can be implemented for arbitrary types
--> src\main.rs:3:1
|
3 | impl Add<i32> for i32 {
| ^^^^^--------^^^^^---
| | | |
| | | `i32` is not defined in the current crate
| | `i32` is not defined in the current crate
| impl doesn't use only types from inside the current crate
|
= note: define and implement a trait or new type instead
这个就是孤儿规则在起作用:当你为某类型实现某 trait 的时候,该类型或者trait至少有一个是在当前 crate 中定义的,你不能为第三方的类型实现第三方的 trait 。
5、Trait与泛型
trait Hello<T> {
fn world<T>(&self, i: T) -> String;
}
上述就是一个简单的携带泛型T的trait
6、Trait用途一:接口抽象
trait Hello {
fn say_hi(&self) {
println!("hi");
}
}
struct Student {}
impl Hello for Student {}
fn main() {
let s = Student {};
s.say_hi();
}
7、Trait用途二:泛型约束
比如要编写一个函数,但是只能对指定类型生效,这个时候可以借助Trait作为泛型的约束,称之为Trait Bound(Trait限定),当然Trait Bound可以同时存在多个,用"+"连接
trait Run {
}
trait Eat {
}
#[derive(Debug)]
struct Horse {
}
impl Run for Horse {
}
impl Eat for Horse {
}
fn demo<T: Run + Eat>(x: T) {}
//如下方式是等价的
//fn demo<T>(x: T) where T: Run + Eat {}
泛型约束还可以作用于trait本身
trait Learning {}
trait Teaching: Learning {}
struct Student {}
impl Learning for Student {}
struct Teacher {}
impl Learning for Teacher {}
impl Teaching for Teacher {}
表明只有实现了 Learning 的trait类型才能实现 Teaching trait
8、Trait用途三:作为抽象类型 - Trait Object(Trait对象,动态分发)
当我们只关心某个类型是否实现了特定trait,而不关注其具体类型的时候,就该Trait Object(Trait对象)登场了,请看下面两个例子的差别,前者使用泛型+Trait bound,后者使用Trait对象
泛型模式:
trait Run {}
struct Human {}
impl Run for Human {}
struct Cat {}
impl Run for Cat {}
fn demo<T>(x: Vec<Box<T>>) where T: Run {}
fn main() {
let mut v = vec![];
v.push(Box::new(Human {}));
//v.push(Box::new(Cat {})); //这行会导致编译失败
demo(v);
}
Trait对象模式:
trait Run {}
struct Human {}
impl Run for Human {}
struct Cat {}
impl Run for Cat {}
fn demo(x: Vec<Box<dyn Run>>) {}
fn main() {
let mut v: Vec<Box<dyn Run>> = vec![];
v.push(Box::new(Human {}));
v.push(Box::new(Cat {}));
demo(v);
}
Trait object的本质是指针,它可以指向不同的类型,指向的具体类型不同,调用的方法也不同。
对象安全
一个Trait Object是指实现了一组Traits的某个类型的不确定值,这组Traits由对象安全的Trait以及auto traits组成,即一个Trait是对象安全的,如果满足:
Trait本身是没有Self: Sized约束- 所有方法都是
Object Safe(对象安全)的
所有方法都必须满足如下约束才能成为是Object Safe的
- 函数有
Self: Sized约束, - -------------或者满足如下所有条件---------------
- 函数不能有泛型参数
- 第一个参数必须为
Self类型或者可以解引用为Self的类型(目前包含self&self&mut selfself:: Box<Self>) - 其他参数或者返回值均不能使用
Self类型

9、Trait用途四:作为抽象类型 - impl Trait
impl TraitName可以作为返回值类型(更准确的说,impl TraitName并不是一个类型),也可以作为函数参数,2018版本,impl Trait只能出现在这两个位置。该使用方式在Closure和Iterator中十分有用,以后介绍;
trait Run {
}
struct Human {
}
impl Run for Human {
}
fn demo() -> impl Run {
Human {}
}
fn main() {
}
特别需要指出的是,函数的不同分支的返回值需要为同一个具体类型,如下方式将会编译失败。
trait Run {
}
struct Human {
}
impl Run for Human {
}
struct Cat {
}
impl Run for Cat {
}
fn demo(x: i32) -> impl Run {
if x > 0 {
Human {}
} else {
Cat {}
}
}
fn main() {
}
10、Trait中的所有权
Trait方法如果接受了 self 类型的参数,则会消耗类型的值自身,比如
trait Run {
fn run_and_die(self);
}
#[derive(Debug)]
struct Horse {
}
impl Run for Horse {
fn run_and_die(self) {
}
}
fn main() {
let h = Horse {};
h.run_and_die();
//println!("{:?}", h); //这行会导致失败,因为run_and_die会消耗自己
}
11、Derive
编译器允许你通过 #[derive] 属性自动实现一些Trait,这些Trait包含:
- 比较相关的:
Eq,PartialEq,Ord,PartialOrd Clone,经由&T创建TCopy,实现T的复制语义Hash,计算&T的哈希值Default,创建数据类型的空实例Debug,使得可以用{:?}格式化T
你既可以通过#[derive]自动实现这些Trait,也可以自己手动去impl这些Trait
#[derive(Debug,Copy, Clone)]
struct Person {}
12、Unsafe Trait
带有unsafe关键字定义的Trait,使用unsafe Trait 是safe的,一个类型实现unsafe Trait的时候,必须使用unsafe impl作为前缀,比如Send和Sync是unsafe的,则impl这两个Trait如下
unsafe impl Send for Student {}
13、常见的Trait
a. Deref,DerefMut : 可以用来重载 *操作符,也可以用来 method resolution 以及 deref coercions(解引用转换)
b. Drop: 用于解构,在一个类型的变量被销毁前执行
c. Copy :如果类型实现了这个Trait,则该类型的值会使用拷贝语义替代移动语义,并避免所有权的变更。一个类型实现Copy有两个前提条件:1、这个类型不能实现Drop;2、这个类型的所有字段都必须为Copy的。
编译器会为以下类型自动实现Copy ,除此之外的类型如果想要impl Copy必须先impl Clone:
- Numeric types
char,bool以及!- 由
Copy类型组成的Tuples - 由
Copy类型组成的Arrays Shared references(共享引用/借用)Raw pointers(裸指针)
d. Clone:Copy的超集,它也是需要编译器生成implementations,编译器会为以下类型自动实现Clone:
- 内置实现了
Copy的类型 - 由
Clone类型组成的Tuples - 由
Clone类型组成的Arrays
e. Send:表明一个类型的值是否可以安全的在线程间传递。
f. Sync:表明一个类型的值是否可以安全的在线程间共享。
g. Sized:编译器可确定大小的类型
i. Unsize:编译器无法确定大小的类型
14、Operator Traits
std::cmp中定义的Traits
std::ops中定义的Traits
可以用来重载操作符,slice索引表达式(indexing expressions) 以及 调用表达式(call expressions)
15、Auto traits
Send,Sync,UnwindSafe,RefUnwindSafe
16、消除Trait歧义(Disambiguating overlapping traits)
假设为某一个类型,实现了两个Trait,这两个Trait刚好有同名的method,这个时候如何区分呢?
trait TraitOne {
fn action(&self) {
println!("action of trait one!")
}
}
trait TraitTwo {
fn action(&self) {
println!("action of trait two!");
}
}
struct Person {}
impl TraitOne for Person {}
impl TraitTwo for Person {}
fn main() {
let p = Person {};
<Person as TraitOne>::action(&p);
<Person as TraitTwo>::action(&p);
//如下方式会编译失败
//p.action();
}
17、参考资料
Reference:https://doc.rust-lang.org/stable/reference/
rust by example:https://doc.rust-lang.org/stabl
浙公网安备 33010602011771号