别再忽略它!Trait约束在Rust中的逆天用法,看完震惊

390 阅读4分钟

在rust中Trait 约束用于指定一个泛型类型必须实现的特定 Trait。Trait 约束允许你在泛型类型参数列表中指定类型必须满足的条件,这样可以在编译时确保类型具有某些行为 下面我们将从

  • trait约束语法
  • 多个trait约束
  • 生命周期约束
  • 默认trait实现
  • 高级trait约束
  • 函数返回中的 impl Trait

trait约束语法

fn generic_function<TTrait>(arg: T) {
    // 在这里可以使用 Trait 中的方法
}

在这个例子中,T 是一个泛型类型参数,: Trait 表示 T 必须实现 Trait。下面我们给出一个具体的例子来具体展示怎么使用trait的约束怎么使用:

trait Printable {
    fn print(&self);
}

struct Point {
    x: i32,
    y: i32,
}

impl Printable for Point {
    fn print(&self) {
        println!("Point({}, {})"self.x, self.y);
    }
}

fn print_if_printable<T: Printable>(item: T) {
    item.print();
}

fn main() {
    let p = Point { x: 1, y: 2 };
    print_if_printable(p);
}

在这个例子中,Printable 是一个 Trait,它有一个 print 方法。Point 结构体实现了 Printable Trait。print_if_printable 函数接受任何实现了 Printable 的类型 T,并调用它的 print 方法。

多个 Trait 约束

我们可以为一个泛型指定多个trait,语法如下:

fn generic_function<TTrait1 + Trait2>(arg: T) {
    // 在这里可以使用 Trait1 和 Trait2 中的方法
}

这里 T 必须同时实现 Trait1 和 Trait2,例如:

trait Drawable {
    fn draw(&self);
}

trait Erasable {
    fn erase(&self);
}

struct Shape {
    color: String,
}

impl Drawable for Shape {
    fn draw(&self) {
        println!("Drawing shape with color {}"self.color);
    }
}

impl Erasable for Shape {
    fn erase(&self) {
        println!("Erasing shape with color {}"self.color);
    }
}

fn process<T: Drawable + Erasable>(item: T) {
    item.draw();
    item.erase();
}

fn main() {
    let shape = Shape { color: "red".to_string() };
    process(shape);
}

在这个例子中,Shape 结构体实现了 Drawable 和 Erasable 两个 Trait。process 函数接受任何同时实现了 Drawable 和 Erasable 的类型 T,并依次调用它的 draw 和 erase 方法。

生命周期约束

除了 Trait 约束,你还可以为泛型类型指定生命周期约束:

fn generic_function<'a, T>(arg: &'a T) {
    // 这里 'a 是一个生命周期参数,T 必须至少拥有 'a 生命周期
}

下面我们给出生命周期约束的具体例子:

fn longest<'a>(x: &'a str, y: &'a str-> &'a str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}

fn main() {
    let string1 = String::from("long string is long");
    let result;
    {
        let string2 = String::from("xyz");
        result = longest(string1.as_str(), string2.as_str());
    }
    println!("The longest string is: {}", result);
}

在这个例子中,longest 函数接受两个字符串切片引用,并返回它们中较长的一个。这里的 'a 是一个生命周期参数,表示两个输入字符串切片必须至少拥有 'a 生命周期。

默认 Trait 实现

如果一个类型已经为某个 Trait 提供了默认实现,你可以在泛型函数中直接使用该实现:

trait Runable{
  fn run(&self){
     println!("任何动物都会跑");
  }
}

struct Animal{
   name:String,
}
impl Runable for Animal{
}

main(){
   let animal = Animal{
     name:String::from("dog"),
   };
   animal.run();
}

我们首先定义一个Runable的trait,里面有一个默认实现的方法,打印出来“任何动物都会跑”。然后定义一个结构体Animal用来实现Runable trait,然后在main方法中创建animalAnimal类型的变量,然后animal.run()用来调用Runable的trait的默认实现方法。

高级 Trait 约束

Rust 还支持高级的 Trait 约束,如:

  • ?Sized:允许泛型类型是大小未知的类型(比如 trait 对象)。
  • 高级生命周期约束:如 'static,表示类型拥有 'static 生命周期。

函数返回中的 impl Trait

/// 定义一个名为Summary的trait
trait Summary{
  fn summary(&self)->String;
}

fn returns_summarizable() -> impl Summary {
    Weibo {
        username: String::from("sunface"),
        content: String::from(
            "m1 max太厉害了,电脑再也不会卡",
        )
    }
}

因为 Weibo 实现了 Summary,因此这里可以用它来作为返回值。要注意的是,虽然我们知道这里是一个 Weibo 类型,但是对于 returns_summarizable 的调用者而言,他只知道返回了一个实现了 Summary 特征的对象,但是并不知道返回了一个 Weibo 类型。

这种 impl Trait 形式的返回值,在一种场景下非常非常有用,那就是返回的真实类型非常复杂,你不知道该怎么声明时(毕竟 Rust 要求你必须标出所有的类型),此时就可以用 impl Trait 的方式简单返回。例如,闭包和迭代器就是很复杂,只有编译器才知道那玩意的真实类型,如果让你写出来它们的具体类型,估计内心有一万只草泥马奔腾,好在你可以用 impl Iterator 来告诉调用者,返回了一个迭代器,因为所有迭代器都会实现 Iterator 特征

本文使用 markdown.com.cn 排版