Traits

  • 定义行为在多个类型中共享。
  • 可以定义默认行为在实现者中间共享。
  • 可以用于定义参数的行为,同样可以定义返回值行为,当用 trait 限定返回值类型时,不能同时(if/else)返回多种实现了该 trait 的类型。
pub trait Summary {
	fn summarize(&self) -> String;
}

pub struct Article{
		pub title: String,
}

impl Summary for Article {
	fn summarize(&self) -> String {
		format!("{}", self.title)
	}
}

pub fn notify(item: impl Summary) {
	println!("{}", item.summarize());
}

// trait bound 语法糖版本
pub fn notify<T: Summary>(item: T) {
	println!("{}", item.summarize());
}

定义参数行为

  • 通过 implfn notify(item: impl TraitName) ,用于简单明了的场景,比如一个参数
  • 通过 trait boundfn notify<T: TraitName> (item: T) ,用于更复杂的场景,比如多个参数用于减少代码
    • 可以通过 + 连接: fn notify(T: TraitName + Display) (item: T)

    • 可以通过 where 子句

      fn some_function<T: Display + Clone, U: Clone + Debug>(t: T, u: U) -> i32 {
      
      // vs
      
      fn some_function<T, U>(t: T, u: U) -> i32
      where T: Display + Clone,
      			U: Clone + Debug
      {
      

Trait Objects

对比泛型:

  • 泛型会在编译期展开:将确定的类型替换泛型参数展开成非泛型的实现。方法调用在编译期就能确定。 – 静态分配
  • Trait Object 在编译期确定方法调用。 – 动态分配

只能使用 对象安全(object-safe) 的特性作为 Trait Object 。对象安全的特性定义的所以方法必须满足如下规则:

  • 所有方法返回类型不能是 Self
  • 所有方法不包含泛型形参

黄金规则:我们必须将一些动态大小的类型的值放在指针后面,通过指针引用。

每一个 trait 都是一个动态大小的类型,如果要将 trait 当作对象使用必须通过指针引用,如:

  • &dyn Trait
  • Box<dyn Trait>
  • Rc<dyn Trait>

Sized 特性用于标志类型大小是否编译期可知,并且在编译期自动为所有内容都实现。

fn generic<T>(t: T) {
 // --snip--
}

// 等于
fn generic<T: Sized>(t: T) {
	// --snip--
}

可以通过 ?Sized 来避免这种默认行为:

fn generic<T: ?Sized>(t: &T) {
	// --snip--
}