使用 trait
trait (trait)类似 Java 中的 Interface。其语法为:
impl Trait for Struct {
// Trait 定义的函数
}trait Descriptive {
fn describe(&self) -> String;
}
struct Person {
name: String,
age: u8,
}
impl Descriptive for Person {
fn describe(&self) -> String {
return fromat!("{}今年 {} 岁了", self.name, self.age);
}
}
fn main() {
let a = Person {
name: String::from("小明"),
age: 20,
};
println!("{}", a.describe());
}| trait 可以带有默认实现 |
impl dyn trait
impl dyn trait 能够动态为 trait 添加方法,此方法在 trait 的所有对象上均可用,但是实现 trait 时无需(也不能)覆盖此方法。
假设有以下场景:
现在有如下 trait:
trait AsBool {
fn as_bool(&self) -> bool
}现在由于 !xxx.AsBool() 使用的非常频繁,因此欲添加 is_false 方法:
trait AsBool {
fn as_bool(&self) -> bool;
fn is_false(&self) -> bool {
!self.as_bool()
}
}然而由于 is_fase 是 trait 的一部分,因此用户可能覆盖默认实现,并且违反 x.as_bool() == !x.is_fase() 约定。
可以将其实现为静态函数:
fn is_false(x: impl AsBool) -> bool {
!x.as_bool()
}然后这样调用方式变为了 is_false(x) ,违反了习惯用法。
这种情况下最好使用 impl dyn trait 方法:
impl dyn AsBool {
fn is_false(&self) -> bool {
!self.as_bool()
}
}这样,用户可以以 x.as_false() 的方式调用,但是由于 is_false 不是 trait 的一部分,又无法覆盖。
在某些情况下可能需要手动将类型转为指定类型。例如: |
在调用的时候需要手动进行类型转换:
let frame = <dyn AsyncWrite + Unpin as FrameRead>::read_frame(obj)?;trait 作为参数和返回值
最简单的方式是:
fn notify(item: &impl Summary) -> impl Summary {
}另外,接受 trait 的函数还有一种简便的表示方式:
fn output<T: Descriptive>(obj: &T) {
println!("{}", obj.describe());
}trait 可以进行组合:
fn output<T: Display + Clone>(obj: &T) {
println!("{}", obj.describe());
}此时类型 T 必须同时实现了 Display 和 Clone
还能使用 where 简化函数签名:
fn output<T, U>(obj: &T)
where
T: Display + Clone,
U: Clone + Debug,
{
println!("{}", obj);
}auto trait
autotrait 会自动为每个类型实现此 tait,除非显式将此 trait 禁用。
例如 Send 和 Sync:
pub unsafe auto trait Send { }
impl !Send for Args {} // 禁止为 Args 实现 Send显示标注 trait 类型
由于 Rust 类型推断的问题,某些情况下需要使用 Arc<dyn MyTrait>,但是 Rust 将其推断为具体类型。对于这种情况,需要手动标注类型:
let x: Arc<dyn MyTrait> = MyType::new();dyn 兼容
若一个 trait 是 dyn 兼容(以前叫对象安全),则这个 trait 可以执行动态分发。尽管很多情况下使用 trait 是为了使用动态分发,但是也有很多场景 trait 只是为了规定接口。
由于 dyn 兼容影响的是动态分发,因此只要 trait 不影响 vtable 的构建,就是 dyn 兼容的。
dyn 兼容的 trait 具有下面的属性:
它的所有 supertraits 也是 dyn 兼容的。
supertaits 不能存在 Sized。这是因为若一个 trait 是 Sized,则它的大小在编译期就可以确定下来,而动态分发要求 !Sized。
不能有任何关联常量。
不能有任何泛型参数。
所有的关联函数要么可以通过 trait 对象调度,要不是显式不可调度的:
可调度的函数是:
没有任何类型参数(除了生命周期参数)。
self 只能是 &Self, &self, &mut Self, &mut self, Box<Self>, Rc<Self>, Arc<Self>, Pin<P>(P 是前述类型之一)。
不能有不透明的返回类型。也就是说返回值不能存在 impl Trait。
显式不可调度的函数有:
带有 where Self:sized 限定的参数。