使用 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 的一部分,又无法覆盖。

在某些情况下可能需要手动将类型转为指定类型。例如:

impl FrameRead for dyn AsyncRead + Unpin {
    async fn read_frame(&mut self) -> io::Result<Frame> {
        todo!()
    }
}

在调用的时候需要手动进行类型转换:

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 限定的参数。

Last moify: 2025-03-27 01:12:58
Build time:2025-12-15 13:00:34
Powered By asphinx