23、Trait Bounds(Traits约束)

Trait Bounds(Traits约束)

我们已经看到了两种traits的用例:

  • 解锁“内置”行为(例如:运算符重载)
  • 为已经存在的类型添加新的行为(例如:为u32类型添加is_even方法)

现在是第三种用例:泛型编程

引入问题

到目前为止,我们的所有函数和方法都是和具体类型一起工作的。

基于具体类型编写的代码往往更容易阅读和理解,但是它的可复用性也受到了限制。

 

例如,我们可以想象一下,编写一个函数,如果整数是偶数,就返回true。如果我们使用具体类型编程,那么我们必须为每个整数类型编写一个单独的函数!

一共有多少个整数类型呢??

这很显然,作为程序员,怎么能干这种事情。

 

如果不用泛型解决这个问题,似乎确实有好一点的方案?

我们可以编写一个拓展trait,然后为每个整数类型编写不同的实现:

trait IsEven {
    fn is_even(&self) -> bool;
}

impl IsEven for i32 {
    fn is_even(&self) -> bool {
        self % 2 == 0
    }
}

impl IsEven for i64 {
    fn is_even(&self) -> bool {
        self % 2 == 0
    }
}

// Etc.

但是,这也好不到哪去吧~

重复仍然存在

泛型编程

使用泛型可以做的更好

泛型允许我们编写使用类型参数,而不是具体类型的代码:

fn print_if_even<T>(n: T)
where
    T: IsEven + Debug
{
    if n.is_even() {
        println!("{n:?} is even");
    }
}

print_if_even就是一个泛型函数。

它不与特定类型绑定,而是可以与任意类型T一起使用:

  • 这个类型实现了IsEventrait
  • 这个类型实现了Debugtrait

以上的这个约束由trait bound表示:T:IsEven+Debug+操作符用于要求T实现多个trait。

T:IsEven+Debug相当于T实现IsEven和Debug

 

Traits约束

Traits约束在print_if_even中的作用就是

如果我们尝试删掉Traits约束,代码将无法通过编译:

fn print_if_even<T>(n: T) {
    if n.is_even() {
        println!("{n:?} is even");
    }
}

image-20250910081124926

 

没有traits约束,编译器就不知道T类型可以做什么,他不知道T有is_even方法,也不知道如何对T类型进行格式化打印。从编译器的角度看,裸露的T泛型根本就没有行为。

 

traits约束通过确保函数体所需行为的存在,限制了可以使用的类型集。

语法:内连traits约束

上面的例子都使用where子句指定traits约束。

fn print_if_even<T>(n: T)
where
    T: IsEven + Debug
//  ^^^^^^^^^^^^^^^^^
//  This is a `where` clause
{
    // [...]
}

如果traits边界比较简单,可以将它们内联到类型参数旁边

fn print_if_even<T: IsEven + Debug>(n: T) {
    //           ^^^^^^^^^^^^^^^^^
    //           This is an inline trait bound
    // [...]
}

语法:有意义的名称

上面的例子使用到了T作为类型参数。这是一般情况下,函数只有一个类型参数时的常见的约定。

我们可以使用其他的有意义的名称作为类型参数:

fn print_if_even<Number: IsEven + Debug>(n: Number) {
    // [...]
}

 

当涉及到多个类型参数时,最好使用更加有意义的名称,在命名类型参数时,要尽可能提高清晰度和可读性。

注意遵守Rust的编码规范:使用驼峰命名法命名类型参数

练习

image-20250910084154704

这次的题目,是让我们补全上面泛型函数的traits约束,实现比较功能,查阅资料,寻找支持比较的traits即可,这边我发现

image-20250910084301608

就是这个PartialOrd实现了比较的功能,所以我们导入这个trait即可

pub fn min<T:PartialOrd>(left: T, right: T) -> T {
    if left <= right {
        left
    } else {
        right
    }
}

也是顺利通过了。

 

这一节的学习就先到这里了。

阅读剩余
THE END