Skip to main content
  1. internet/

Rust设计模式之扩展trait

·976 words·2 mins·

Extension Trait Pattern #

扩展trait是一种设计模式,常用于扩展所依赖的外部类型的能力。

比如说在rust中常用println来打印内容:

fn main() {
    let hello = "Hello, world!".to_string();
    println!("{}", hello);
}

hello的类型是String,没有实现Display trait, 因此无法直接println(helo),而是通过占位符的方式进行打印。

如果代码里有非常多的地方需要打印,那么会非常麻烦。要是可以直接使用print方法就好了。

这时就可以使用扩展trait来实现这个功能。

trait StringExt {
    fn print(&self);
}

impl StringExt for String {
    fn print(&self) {
        println!("{}", self);
    }
}

fn main() {
    let hello = "Hello, world!".to_string();
    hello.print();
}

为外部类型新增方法 #

上面的例子中,我们为String类型新增了一个print方法, 只需要以下几个步骤:

  1. 定义一个trait, 通常以类型名称开头,以Ext结尾。
  2. String类型实现这个trait
  3. 在实现中增加方法print
  4. 在需要的地方使用这个方法。

让我们再实现一个练练手: 为动态数组添加一个sum方法:

trait VecExt<T> {
    fn sum(&self) -> T;
}

impl VecExt<i32> for Vec<i32> {
    fn sum(&self) -> i32 {
        self.iter().sum()
    }
}

fn main() {
    let numbers = vec![1, 2, 3, 4, 5];
    println!("{}", numbers.sum()); // Outputs: 15
}

为外部trait新增方法 #

很多依赖库的方法返回值是trait,为外部trait新增方法能够减少很多重复的代码。

让我们为std::error::Error实现StringExt:

impl StringExt for std::error::Error {
    fn print(&self) {
        println!("{}", self);
    }
}

上边的代码会编译失败,因为Rust不允许为外部trait实现本地trait。但是我们可以"曲线救国":

impl<T: std::error::Error> StringExt for T {
    fn print(&self) {
        println!("{}", self);
    }
}

这个代码是可以编译通过的,因为我们不是在为std::error::Error实现本地trait, 而是为所有实现了std::error::Error的具体类型实现本地trait!!

测试一下:

use std::fs::File;

trait StringExt {
    fn print(&self);
}

impl<T: std::error::Error> StringExt for T {
    fn print(&self) {
        println!("{}", self);
    }
}

fn main() {
    let result = File::open("non_existent_file.txt");

    match result {
        Ok(_) => println!("File opened successfully."),
        Err(e) => e.print(), // Outputs: No such file or directory (os error 2)
    }
}

孤儿限制 #

Rust通过孤儿规则来保证类型安全,其具体内容如下:

  1. 可以为任意类型实现本地trait
  2. 可以为本地类型实现外部trait
  3. 不能为外部类型实现外部trait

第一条我们在上述内容中已经看到了,第二条是trait的基本使用,第三条需要注意下: 如果我们为外部类型String实现外部trait std::error::Error会发生什么?

如果我们可以这么做的话,别人也可以这么做。这意味程序中可能会有多个实现,而编译器无法判断应该使用哪一个。这就是孤儿限制的缘由。

推荐阅读 #