定义和实现 Trait

定义 trait 比较简单, 其语法如下:

#![allow(unused)]
fn main() {
pub trait TraitName {
  fn method1(&self, ...) -> Retrun1;
  fn function2(...) -> Return2;
  ...
}
}

先看一下标准库中 fmt::Debug trait:

#![allow(unused)]
fn main() {
pub trait Debug {
    // Required method
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result;
}
}

该 trait 只声明了一个方法 fmt(), 要为自己定义的类型实现这个方法, 也很简单:

#![allow(unused)]
fn main() {
use std::fmt;

pub struct Point {
    x: f32,
    y: f32,
}

impl fmt::Debug for Point {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("Point")
            .field("x", &self.x)
            .field("y", &self.y)
            .finish()
    }
}
}

另一个常用的 trait 是 Default trait, 用于实现类型的默认值, 其定义如下:

#![allow(unused)]
fn main() {
pub trait Default: Sized {
    // Required method
    fn default() -> Self;
}
}

接下来给上面的 Point 结构体实现这个 trait:

#![allow(unused)]
fn main() {
impl Default for Point {
    fn default() -> Self {
        Self {
            x: 0.0,
            y: 0.0,
        }
    }
}
}

实现方法

在 trait 中, 可以只声明方法 (method declaration), 也可以同时定义方法 (method default definition), 即编写该方法的默认实现, 但可以被外部类型所覆盖.

看一下标准库中的例子:

#![allow(unused)]

fn main() {
}

继承 trait

#![allow(dead_code)]

trait Person {
    fn name(&self) -> String;
}

trait Student: Person {
    fn university(&self) -> String;
}

trait Programmer {
    fn favorite_language(&self) -> String;
}

trait ComputerScienceStudent: Programmer + Student {
    fn git_username(&self) -> String;
}

fn comp_sci_student_greeting(student: &dyn ComputerScienceStudent) -> String {
    format!(
        "My name is {} and I attend {}. My git username is {}",
        student.name(),
        student.university(),
        student.git_username()
    )
}

fn main() {}

空的 trait

标准库中定义了好几个空的 trait, 这些 trait 只有名称, 没有约束任何的方法或者别的类型, 比如:

  • Sized
  • Copy
  • Send
  • Sync

声明它们的代码很简单:

#![allow(unused)]
fn main() {
pub trait Copy: Clone { }
pub trait Sized { }
pub unsafe auto trait Send { }
pub unsafe auto trait Sync { }
}

通常这些类型都被编译器使用:

  • Copy trait 可以让类型通过拷贝比特位来复制其值
  • Send/Sync, 用于实现跨线程访问共享的状态
  • Sized, 要求类型在编译期有确定的内存大小占用, 否则就是 dynamic sized type

为外部类型实现外部的 trait

Rust 语言中的规则是, 要么类型是自己定义的, 要么 trait 是自己声明的, 这样的话才能给类型实现指定的 trait.

但有时候要给标准库或者第三方库里的类型实现外部的 trait, 怎么处理? 此时可以先定义一个新的结构体, 然后给这个结构体实现已存在的 trait, 这个方法被称为 New Type Idiom.

标准库并没有为 f32f64 实现 Eq, Ord, Hash 等 traits, 但有时又确实需要实现这些 trait, 看下面的一个简陋的例子:

#![allow(unused)]
fn main() {
use std::cmp::Ordering;
use std::hash::{Hash, Hasher};

#[derive(Debug, Default, Clone, Copy, PartialEq, PartialOrd)]
pub struct F32(f32);

impl Eq for F32 {}

#[allow(clippy::derive_ord_xor_partial_ord)]
impl Ord for F32 {
    fn cmp(&self, other: &Self) -> Ordering {
        self.0.to_be_bytes().cmp(&other.0.to_be_bytes())
    }
}

impl Hash for F32 {
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.0.to_bits().hash(state);
    }
}

#[cfg(test)]
mod tests {
    use super::F32;

    #[test]
    fn test_f32_equal() {
        let f1 = F32(std::f32::consts::PI);
        let f2 = F32(std::f32::consts::PI);
        assert_eq!(f1, f2);
    }
}
}

参考