在面向对象编程语言,例如 Java,要实现一些相同行为的场景,就需要通过 interface 来实现,rust 也有类似 interface 的特性,就叫做 traits 。简单来说,traits 是定义共享行为的方式

简单例子

定义一个 traits 以 trait 开头,后面跟着特质的名称,然后在大括号中定义方法签名,方法签名是一个方法的名称、参数列表和返回值类型,但不包括方法体。

我们来定一个 Summary trait,它有一个 summarize 方法,返回一个字符串:

// src/lib.rs
pub trait Summary {
fn summarize(&self) -> String;
}

接下来我们实现这个特质:

// src/lib.rs
pub struct Tweet {
pub username: String,
pub content: String,
}
// 为 Tweet 结构体实现 Summary 特质
impl Summary for Tweet {
fn summarize(&self) -> String {
format!("{}: {}", self.username, self.content)
}
}

最后可以这样调用

// src/main.rs
use hello_world::{Summary, Tweet};
fn main() {
let tweet = Tweet {
username: String::from("kelen"),
content: String::from(
"kelen.cc is a blog about programming, software and technology.",
),
};
println!("1 new tweet: {}", tweet.summarize());
}

在 main.rs 中使用 Tweet 必须要引入 Summary 特质,这样就可以调用 summarize 方法。这是因为 rust 不会自动将所有可能的方法都引入作用域,所以必须要显式引入。如果不将 Summary 引入作用域,编译器就无法找到 summarize 方法。

通过这个简单例子,可以看到 traits 的使用方法,它可以定义一些共享的行为,然后由具体的类型去实现这些行为,统一不同类型的行为。

默认实现

traits 可以定义默认实现,这样当某个类型没有实现某个方法时,可以调用默认实现。

pub trait Drawable {
fn draw(&self) {
println!("绘制一个默认图形。");
}
}
pub struct Circle {
pub radius: f64,
}
impl Drawable for Circle {} // 不实现 draw 方法
fn main() {
let c = Circle { radius: 1.0 };
c.draw(); // print:绘制一个默认图形。
}

在上面的例子中,Circle 结构体没有实现 draw 方法,但是由于 Drawable 特质有默认实现,所以可以调用 draw 方法。

Trait Bound

Trait Bound(特质边界)是一种约束,它可以指定泛型是某个特质的类型。在泛型定义中使用 Trait Bound 语法,可以限制泛型的类型。

// 定义 Shape 特质
trait Shape {
fn area(&self) -> f64;
}
// 定义 Rectangle 结构体
struct Rectangle {
width: f64,
height: f64,
}
// 为 Rectangle 实现 Shape 特质
impl Shape for Rectangle {
fn area(&self) -> f64 {
self.width * self.height
}
}
// 定义 Circle 结构体
struct Circle {
radius: f64,
}
// 为 Circle 实现 Shape 特质
impl Shape for Circle {
fn area(&self) -> f64 {
std::f64::consts::PI * self.radius * self.radius
}
}
// 泛型函数 print_area,带有 Shape 特质边界
fn print_area<T: Shape>(shape: T) {
println!("面积是: {}", shape.area());
}
fn main() {
// 创建 Rectangle 和 Circle 实例
let rectangle = Rectangle { width: 5.0, height: 3.0 };
let circle = Circle { radius: 2.0 };
// 调用泛型函数 print_area
print_area(rectangle);
print_area(circle);
}

使用 where 子句

where 子句用于在 Rust 中为泛型类型(如结构体、枚举)等指定额外的约束条件。它主要用于处理复杂的特质(Trait)约束,使代码更具可读性。

语法如下:

fn some_function<T, U>(t: T, u: U) where T: Display, U: Clone {
println!("t: {}", t);
let cloned_u = u.clone();
}

这里的 where 子句指定了泛型参数 T 必须实现 Display 特质,泛型参数 U 必须实现 Clone 特质。

如果有多个 Trait 界限,可以使用 where 子句来简化代码。(说白了就是把参数约束挪动到参数后面,代码量还是一样)

use std::fmt::Debug;
fn print_and_clone<T: Clone + Debug>(item: T) {
println!("{:?}", item);
let cloned = item.clone();
println!("{:?}", cloned);
}
fn main() {
let value = 42;
print_and_clone(value);
}

这时候使用 where 子句就可以是函数签名更加简洁。

use std::fmt::Debug;
fn print_and_clone<T>(item: T) where T: Clone + Debug {
println!("{:?}", item);
let cloned = item.clone();
println!("{:?}", cloned);
}
fn main() {
let value = 42;
print_and_clone(value);
}

where 子句可以简化代码,但是不能简化泛型函数的签名。

返回实现了特质的类型

有时候,我们需要返回实现了特质的类型,可以使用 impl Trait 语法。

// 定义 Shape 特质
trait Shape {
fn area(&self) -> f64;
}
// 定义 Rectangle 结构体
struct Rectangle {
width: f64,
height: f64,
}
// 为 Rectangle 实现 Shape 特质
impl Shape for Rectangle {
fn area(&self) -> f64 {
self.width * self.height
}
}
// 返回实现了 Shape 特质的具体类型
fn create_shape() -> impl Shape {
Rectangle { width: 5.0, height: 3.0 }
}
fn main() {
let shape = create_shape();
println!("面积是: {}", shape.area());
}

总结

traits 是一种定义共享行为的方式,可以为不同的类型实现相同的行为。traits 可以定义默认实现,当某个类型没有实现某个方法时,可以调用默认实现。Trait Bound 可以限制泛型的类型,使泛型只能是某个特质的类型。