Rust进阶[part5]_trait

Rust进阶[part5]_trait

trait概述

在 Rust 中,trait 是一种定义共享行为的方式。它类似于其他语言中的接口,允许我们定义一组方法签名,然后让不同的类型去实现这些方法。通过 trait,我们可以实现多态性,即不同类型可以以统一的方式处理。

普通实现

  • 使用 trait 关键字来声明一个特征
  • summary 是特征名
  • 在大括号中定义了该特征的所有方法
// 定义一个 trait
trait Summary {
    fn summarize(&self) -> String;
}

// 定义一个结构体
struct NewsArticle {
    headline: String,
    location: String,
    author: String,
    content: String,
}

// 为 NewsArticle 结构体实现 Summary trait
impl Summary for NewsArticle {
    fn summarize(&self) -> String {
        format!("{}, by {} ({})", self.headline, self.author, self.location)
    }
}

// 定义另一个结构体
struct Tweet {
    username: String,
    content: String,
    reply: bool,
    retweet: bool,
}

// 为 Tweet 结构体实现 Summary trait
impl Summary for Tweet {
    fn summarize(&self) -> String {
        format!("{}: {}", self.username, self.content)
    }
}

fn main() {
    let article = NewsArticle {
        headline: String::from("Penguins win the Stanley Cup Championship!"),
        location: String::from("Pittsburgh, PA, USA"),
        author: String::from("Iceburgh"),
        content: String::from("The Pittsburgh Penguins once again are the best \
                              hockey team in the NHL."),
    };

    let tweet = Tweet {
        username: String::from("horse_ebooks"),
        content: String::from("of course, as you probably already know, people"),
        reply: false,
        retweet: false,
    };

    println!("Article summary: {}", article.summarize());
    println!("Tweet summary: {}", tweet.summarize());
}

特征定义与实现的位置

孤儿规则

孤儿规则是 Rust 中的一个重要规则,它规定了特征的实现必须满足以下条件:要么特征在当前的 crate 中定义,要么类型在当前的 crate 中定义。这个规则确保了特征的实现是可预测的,避免了不同 crate 中对同一类型实现同一特征时可能出现的冲突。

例如,我们不能在自己的 crate 中为 Vec<T> 实现标准库中的 Display 特征,因为 Vec<T>Display 都定义在标准库中。

带泛型的 trait

可以针对不同的泛型来实现

默认泛型类型参数

  • 实现 trait 时不指定 Rhs 的具体类型,Rhs 的类型将是默认的 Self 类型,也就是在其上实现 Add 的类型
trait Add<Rhs=Self> {
    type Output;
    fn add(self, rhs: Rhs) -> Self::Output;
}

// 为 i32 实现 Add trait
impl Add for i32 {
    type Output = i32;
    fn add(self, rhs: i32) -> i32 {
        self + rhs
    }
}

fn main() {
    let num1 = 5;
    let num2 = 3;
    let result = num1.add(num2);
    println!("The result of addition is: {}", result);
}

关联类型

关联类型是 trait 定义中的类型占位符,并不定义它的具体类型是什么,在实现这个 trait 的时候才为这个关联类型赋予确定的类型。

trait Container {
    type Item;

    fn contains(&self, item: &Self::Item) -> bool;
}

struct IntContainer {
    numbers: Vec<i32>,
}

impl Container for IntContainer {
    type Item = i32;

    fn contains(&self, item: &i32) -> bool {
        self.numbers.contains(item)
    }
}

fn main() {
    let container = IntContainer { numbers: vec![1, 2, 3] };
    println!("Container contains 2: {}", container.contains(&2));
}

特征作为函数参数

&impl

使用 &impl Trait 语法可以将实现了特定 trait 的类型作为参数传递给函数。

trait Summary {
    fn summarize(&self) -> String;
}

struct NewsArticle {
    headline: String,
}

impl Summary for NewsArticle {
    fn summarize(&self) -> String {
        format!("Headline: {}", self.headline)
    }
}

fn notify(item: &impl Summary) {
    println!("Breaking news! {}", item.summarize());
}

fn main() {
    let article = NewsArticle {
        headline: String::from("New Rust feature released!"),
    };
    notify(&article);
}

trait bound语法

trait bound 语法允许我们在泛型函数中指定泛型类型必须实现的 trait。


fn notify<T: Summary>(item: &T) {
    println!("Breaking news! {}", item.summarize());
}

where 语法简化 trait bound

当泛型参数较多,并且每个参数都有多个 trait 约束时,使用 where 语法可以使代码更易读。



fn notify<T>(item: &T)
where
    T: Summary + Display,
{
    println!("Breaking news! {}", item.summarize());
    item.display();
}

trait作为泛型的类型

可以将 trait 作为泛型类型参数,这样函数就可以接受实现了该 trait 的任何类型。

trait Summary {
    fn summarize(&self) -> String;
}

struct NewsArticle {
    headline: String,
}

impl Summary for NewsArticle {
    fn summarize(&self) -> String {
        format!("Headline: {}", self.headline)
    }
}

fn print_summary<T: Summary>(item: T) {
    println!("Summary: {}", item.summarize());
}

fn main() {
    let article = NewsArticle {
        headline: String::from("New Rust feature released!"),
    };
    print_summary(article);
}

impl trait 语法 (返回值中返回trait)

如果需要动态返回不同的 trait 类型的话,需要使用 Box<dyn xxx> 语法来保证返回的内存大小一致,并且可以标注返回的哪种 trait。

trait Summary {
    fn summarize(&self) -> String;
}

struct NewsArticle {
    headline: String,
}

impl Summary for NewsArticle {
    fn summarize(&self) -> String {
        format!("Headline: {}", self.headline)
    }
}

fn returns_summarizable() -> Box<dyn Summary> {
    Box::new(NewsArticle {
        headline: String::from("New Rust feature released!"),
    })
}

fn main() {
    let item = returns_summarizable();
    println!("Summary: {}", item.summarize());
}

通过 derive 派生类型

例如 #[derive(Debug)] 是一种特征派生语法,这种被标记的对象会自动实现对应的默认特征代码,继承相应的功能。

#[derive(Debug)]
struct Rectangle {
    width: u32,
    height: u32,
}

fn main() {
    let rect = Rectangle { width: 30, height: 50 };
    println!("Rectangle: {:?}", rect);
}

练习

练习 1

定义一个 Area trait,包含一个 area 方法,用于计算面积。然后为 Rectangle 结构体实现该 trait。

练习 2

创建一个函数,接受一个实现了 Area trait 的类型作为参数,并打印其面积。

练习 3

使用 derive 派生 CloneCopy trait 到一个自定义类型上,并验证其功能。

以下是练习的参考答案:

// 练习 1
trait Area {
    fn area(&self) -> f64;
}

struct Rectangle {
    width: f64,
    height: f64,
}

impl Area for Rectangle {
    fn area(&self) -> f64 {
        self.width * self.height
    }
}

// 练习 2
fn print_area<T: Area>(shape: T) {
    println!("The area is: {}", shape.area());
}

// 练习 3
#[derive(Clone, Copy, Debug)]
struct Point {
    x: i32,
    y: i32,
}

fn main() {
    let rect = Rectangle { width: 5.0, height: 3.0 };
    print_area(rect);

    let p1 = Point { x: 1, y: 2 };
    let p2 = p1; // 复制操作
    println!("p1: {:?}, p2: {:?}", p1, p2);
}