Trait(特征)
这个也是 Rust 中最重要的特性之一。
- Trait 可以告诉编译器某种类型具有哪些并且可以和其它类型共享的功能
- 抽象的定义共享行为
- Trait bounds(约束): 泛型类型参数指定为实现了特定行为的类型
- Trait 其实和其它语言的接口 interface 很像,但又有区别
定义 Trait
把方法签名放一起,来定义实现某种目的所必需的一组行为
- 只有方法签名,没有具体实现
- 可以有多个方法,每个方法占一行,以分号;结尾
- 实现该 Trait 的类型必须提供具体的方法实现
pub trait Test {
fn test1(&self) -> String;
fn test2(&self) -> String;
}
fn main() {}
在类型上实现 Trait
与为类型实现方法类似,不同的地方就是 impl XXX for XXX {}
, {}里要对方法的定义进行具体的实现
// src/lib.rs
// 人或动物的描述信息
pub trait Description {
fn description(&self) -> String;
}
pub struct People {
pub name: String,
pub gender: String,
pub age: i32,
pub height: i32,
}
impl Description for People {
fn description(&self) -> String {
format!(
"Student description: name:{}-age:{}-gender:{}-height:{}",
self.name, self.age, self.gender, self.height
)
}
}
pub struct Animal {
pub name: String,
pub action: String,
}
impl Description for Animal {
fn description(&self) -> String {
format!(
"Animal description: name:{}-action: {}",
self.name, self.action
)
}
}
// src/main.rs
// 这里的 demo 是 Cargo.toml 文件里的 [package] 的 name 值
// 把 trait 和 struct 引入
use demo::Description;
use demo::People;
fn main() {
let student = People {
name: String::from("大锤"),
age: 18,
height: 200,
gender: String::from("男"),
};
// People: Student description: name:大锤-age:18-gender:男-height:200
println!("People: {}", student.description())
}
实现 trait 的约束
可以在某个类型上实现 Trait 的前提是,这个类型或者这个 Trait 是在本地 crate 里定义的 。
不能为外部类型来实现外部的 Trait:
- 这个限制是程序属性的一部分
- 更具体说是 孤儿规则,之所以这样命名是因为父类型不存在
- 这个规则能确保别人不能破坏你的代码,反之亦然,你也不能破坏别人的代码
- 如果没有这个规则,两个 crate 可以为同一个类型实现同一个 Trait,Rust 就不知道用哪个实现了
- 也就是说不能为第三方库的类型来实现第三方库的 Trait
默认实现
就是在 Trait 中的方法可以给默认的实现方式,然后 impl trait for struct 的时候不需要手动去实现 Trait 的方法
rustpub trait Description { fn description(&self) -> String { String::from("默认实现") } } pub struct People { pub name: String, pub gender: String, pub age: i32, pub height: i32, } impl Description for People {}
默认实现的方法也可以调用 Trait 里的其它方法,即使调用的方法没有默认的实现
rustpub trait Description { fn description(&self) -> String; fn default_fn(&self) -> String { format!( "description and default function test: {}", self.description() ) } } pub struct People { pub name: String, pub gender: String, pub age: i32, pub height: i32, } impl Description for People { fn description(&self) -> String { // People: 没有进行默认实现的方法: 大锤, 18, 男, 200 format!( "没有进行默认实现的方法: {}, {}, {}, {}", self.name, self.age, self.gender, self.height ) } }
把 Trait 作为函数参数
使用 impl trait 语法:适用于简单的情况
rustpub trait Description { fn description(&self) -> String; fn default_fn(&self) -> String { format!( "description and default function test: {}", self.description() ) } } pub struct People { pub name: String, pub gender: String, pub age: i32, pub height: i32, } impl Description for People { fn description(&self) -> String { // People: 没有进行默认实现的方法: 大锤, 18, 男, 200 format!( "没有进行默认实现的方法: {}, {}, {}, {}", self.name, self.age, self.gender, self.height ) } } pub fn test_trait_fn(item: impl Description) { println!("{}", item.description()) }
使用 Trait bound 语法:适用于复杂的情况,实际就是泛型约束,上面那种方式其实就是这种方式的语法糖
rust// 上面的那个方法改写成这样 pub fn test_trait_fn<T: Description>(item: T) { println!("{}", item.description()) }
如果实现了多个 Trait,可以使用+号连接
rustuse std::fmt::Display; ...... pub fn test_trait_fn1(item: impl Description + Display) { println!("{}", item.description()) } pub fn test_trait_fn2<T: Description + Display>(item: T) { println!("{}", item.description()) }
使用 where 子句来进行泛型约束,上下两种实现方式是等价的
rustuse std::clone::Clone; use std::fmt::{Debug, Display}; // test_trait_fn1 方法的 T 类型实现了 Description 和 Display 两个 Trait,U类型实现了 Debug 和 Clone 两个 Trait pub fn test_trait_fn1<T: Description + Display, U: Debug + Clone>(item1: T, item2: U) { println!("{}", item1.description()) } pub fn test_trait_fn2<T, U>(item1: T, item2: U) where T: Description + Display, U: Debug + Clone, { println!("{}", item1.description()) }
实现了的 Trait 作为返回类型
impl trait 只能返回确定的同一种类型,返回可能出现的不同类型代码会报错,比如函数中判断正确时返回 People,错误时返回 Animal 就会报错。
// 只能返回实现了 Description 这个 Trait 的
pub fn test() -> impl Description {
People {
name: String::from("王大锤"),
gender: String::from("男"),
age: 18,
height: 200,
}
}
看个例子,获取数组里的最大的一项
T 是实现了 PartialOrd 和 Clone 两个 Trait 的类型
fn largest<T: PartialOrd + Clone>(list: &[T]) -> &T {
let mut largest = &list[0];
for item in list.iter() {
if item > largest {
largest = item
}
}
largest
}
fn main() {
let str = vec![String::from("hello"), String::from("world")];
let res = largest(&str);
println!("Largest is {}", res)
}
使用 Trait Bound 有条件的实现方法
在使用泛型类型参数的 impl 块上使用 Trait bound,我们可以有条件的为实现了特定 Trait 的类型来实现方法。下面这段代码的意思就是对于 Pair 来说,不论 T 是什么类型的时候都会有 new 这个方法,而只有 T 的类型是 PartialOrd 和 Display 的时候,它才会有 cmp_display 这个方法。
rustuse std::fmt::Display; struct Pair<T> { x: T, y: T, } impl<T> Pair<T> { fn new(x: T, y: T) -> Self { Self { x, y } } } impl<T: PartialOrd + Display> Pair<T> { fn cmp_display(&self) { if (self.x > self.y) { println!("x > y") } else { println!("x < y") } } } fn main() {}
也可以为实现了其它 Trait 的任意类型有条件的实现某个 Trait
为满足 Trait Bound 的所有类型上实现 Trait 叫做覆盖实现。
常用 Trait
- Clone / Copy trait,约定了数据被深拷贝和浅拷贝的行为;
- Read / Write trait,约定了对 I/O 读写的行为;
- Iterator,约定了迭代器的行为;
- Debug,约定了数据如何被以 debug 的方式显示出来的行为;
- Default,约定数据类型的缺省值如何产生的行为;
- From / TryFrom,约定了数据间如何转换的行为。
内存 Trait
Clone / Copy / Drop
标记 Ttrait
Sized / Send / Sync / Unpin
Sized - 用于标记有具体大小的类型。在使用泛型参数时,Rust 编译器会自动为泛型参数加上 Sized 约束。在少数情况下,需要 T 是可变类型的,Rust 提供了 ?Sized 来摆脱这个约束。
struct Data<T> {
inner: T,
}
fn process_data<T>(data: Data<T>) {
todo!();
}
// 等价于 👇
struct Data<T: Sized> {
inner: T,
}
fn process_data<T: Sized>(data: Data<T>) {
todo!();
}
Send/Sync 是 Rust 并发安全的基础:
- 如果一个类型 T 实现了 Send trait,意味着 T 可以安全地从一个线程移动到另一个线程,也就是说所有权可以在线程间移动。
- 如果一个类型 T 实现了 Sync trait,则意味着 &T 可以安全地在多个线程中共享。一个类型 T 满足 Sync trait,当且仅当 &T 满足 Send trait。
- 如果一个类型 T: Send,那么 T 在某个线程中的独占访问是线程安全的;
- 如果一个类型 T: Sync,那么 T 在线程间的只读共享是安全的。
对于自己定义的数据结构,如果其内部的所有域都实现了 Send / Sync,那么这个数据结构会被自动添加 Send / Sync 。基本上原生数据结构都支持 Send / Sync,也就是说,绝大多数自定义的数据结构都是满足 Send / Sync 的。
标准库中,不支持 Send / Sync 的数据结构主要有:
- 裸指针 *const T / *mut T。它们是不安全的,所以既不是 Send 也不是 Sync。
- UnsafeCell 不支持 Sync。也就是说,任何使用了 Cell 或者 RefCell 的数据结构不支持 Sync。
- 引用计数 Rc 不支持 Send 也不支持 Sync。所以 Rc 无法跨线程。
trait Unpin,是用于自引用类型的
类型转换 Trait
From<T> / Into<T> / AsRef<T> / AsMut<T>
对值类型的转换和对引用类型的转换,Rust 提供了两套不同的 trait:
值类型到值类型的转换:From / Into / TryFrom / TryInto
rustpub trait From<T> { fn from(T) -> Self; } pub trait Into<T> { fn into(self) -> T; } // 在实现 From 的时候会自动实现 Into impl<T, U> Into<U> for T where U: From<T> { fn into(self) -> U { U::from(self) } }
如果数据类型在转换过程中有可能出现错误,可以使用 TryFrom 和 TryInto,用法和 From / Into 一样,只是 trait 内多了一个关联类型 Error,且返回的结果是 Result。
引用类型到引用类型的转换:AsRef / AsMut
rustpub trait AsRef<T> where T: ?Sized { fn as_ref(&self) -> &T; } pub trait AsMut<T> where T: ?Sized { fn as_mut(&mut self) -> &mut T; }
操作符 Trait
Deref / DerefMut
比如 Add trait,它允许你重载加法运算符。Rust 为所有的运算符都提供了 trait,你可以为自己的类型重载某些操作符。
pub trait Deref {
// 解引用出来的结果类型
type Target: ?Sized;
fn deref(&self) -> &Self::Target;
}
pub trait DerefMut: Deref {
fn deref_mut(&mut self) -> &mut Self::Target;
}
// 用于为类型提供缺省值。也可以通过 derive 宏 #[derive(Default)] 来实现,前提是类型中的每个字段都实现了Default trait
pub trait Default {
fn default() -> Self;
}
其它 Trait
Debug / Display / Default
pub trait Debug {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>;
}
pub trait Display {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>;
}