Skip to content

Trait(特征)

这个也是 Rust 中最重要的特性之一。

  • Trait 可以告诉编译器某种类型具有哪些并且可以和其它类型共享的功能
  • 抽象的定义共享行为
  • Trait bounds(约束): 泛型类型参数指定为实现了特定行为的类型
  • Trait 其实和其它语言的接口 interface 很像,但又有区别

定义 Trait

把方法签名放一起,来定义实现某种目的所必需的一组行为

  • 只有方法签名,没有具体实现
  • 可以有多个方法,每个方法占一行,以分号;结尾
  • 实现该 Trait 的类型必须提供具体的方法实现
rust
pub trait Test {
  fn test1(&self) -> String;
  fn test2(&self) -> String;
}

fn main() {}

在类型上实现 Trait

与为类型实现方法类似,不同的地方就是 impl XXX for XXX {}, {}里要对方法的定义进行具体的实现

rust
// 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
    )
  }
}
rust
// 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 的方法

    rust
    pub 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 里的其它方法,即使调用的方法没有默认的实现

    rust
    pub 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 语法:适用于简单的情况

    rust
    pub 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,可以使用+号连接

    rust
    use 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 子句来进行泛型约束,上下两种实现方式是等价的

    rust
    use 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 就会报错。

rust
// 只能返回实现了 Description 这个 Trait 的
pub fn test() -> impl Description {
  People {
    name: String::from("王大锤"),
    gender: String::from("男"),
    age: 18,
    height: 200,
  }
}

看个例子,获取数组里的最大的一项

T 是实现了 PartialOrd 和 Clone 两个 Trait 的类型

rust
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 这个方法。

    rust
    use 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 来摆脱这个约束。

rust
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

    rust
    pub 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

    rust
    pub 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,你可以为自己的类型重载某些操作符。

rust
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

rust
pub trait Debug {
    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>;
}

pub trait Display {
    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>;
}

每天进步一丢丢