前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >【Rust学习】25_特征

【Rust学习】25_特征

原创
作者头像
思索
发布2025-02-14 16:01:19
发布2025-02-14 16:01:19
650
举报
文章被收录于专栏:Rust入门笔记Rust入门笔记

前言

特征(trait)定义了特定类型所具有的并且可以与其他类型共享的功能。我们可以使用特征以抽象的方式定义共享的行为。我们可以使用特征约束来指定泛型类型可以是任何具有特定行为的类型。

内容

注意:特征和其他语言中的接口类似,但存在一些差异。

定义特征

类型的行为由我们能够在该类型上调用的方法构成。如果所有类型都能调用相同的方法,那么这些不同的类型就具有相同的行为。特征(trait)定义是一种将方法签名聚合在一起的手段,用以确定实现特定目的所需的一组行为。

例如,我们可以考虑几个包含不同类型和长度文本的结构体:一个 NewsArticle 结构体,它包含了在特定栏目提交的新闻报道;另一个 Tweet 结构体,它最多可以包含 280 个字符,并带有元数据,指示该推文是新发布的、转发的,还是对另一条推文的回复。

我们计划创建一个名为 aggregator 的媒体聚合器库,它能够展示存储在 NewsArticleTweet 实例中的数据摘要。为了实现这一功能,我们需要从每种类型中获取摘要,这可以通过在实例上调用 summarize 方法来完成。下面的示例代码展示了定义公共 Summary 特征的代码,这个特征用来表达上述行为。

代码语言:rust
复制
pub trait Summary {
    fn summarize(&self) -> String;
}

在这里,我们使用 trait 关键字来声明一个特征,特征的名称在本例中为 Summary。我们还把特征声明为 pub(公开的),这样依赖于我们这个 crate 的其他 crate 也能够使用这个特征,这一点在我们接下来的几个例子中会有所体现。在大括号内部,我们声明了描述特征类型行为的方法签名,在本例中是 fn summarize(&self) -> String

在方法签名之后,我们使用分号来结束声明,而不是在大括号内提供具体实现。每个实现这个 Summary 特征的类型都需要为方法体提供自定义的行为。编译器将确保任何实现了 Summary 特征的类型都必须严格按照这个签名来定义 summarize 方法。

一个 trait 的主体中可以有多个方法:方法签名每行列出一个,每行以分号结尾。

在Type上实现trait

现在我们已经定义了Summary特征所需的方法签名,接下来我们可以在媒体聚合器中实现这些特征。下方的例子展示了如何在NewsArticle结构体上实现Summary特征,其中使用了headlineauthorlocation字段来构建summarize方法的返回值。对于Tweet结构体,我们将summarize方法定义为用户名加上推文的全文,假设推文内容已被限制在280个字符以内。

代码语言:rust
复制
pub trait Summary {
    fn summarize(&self) -> String;
}

pub struct NewsArticle {
    pub headline: String,
    pub location: String,
    pub author: String,
    pub content: String,
}

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

pub struct Tweet {
    pub username: String,
    pub content: String,
    pub reply: bool,
    pub retweet: bool,
}

impl Summary for Tweet {
    fn summarize(&self) -> String {
        format!("{}: {}", self.username, self.content)
    }
}

在类型上实现 trait 与实现常规方法类似,区别在于 impl 后先写 trait 名称,再用 for 关键字指定类型名称,impl 块内放置 trait 定义的方法签名,用花括号填充方法体。实现后,crate 用户可像调用常规方法一样调用实例的 trait 方法,但需同时引入 trait 和类型。例如:

代码语言:rust
复制
use aggregator::{Summary, Tweet};

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

    println!("1 new tweet: {}", tweet.summarize());
}

需注意,只有 trait 或类型(或两者)是本地 crate 时才能实现,如可在本地的 Tweet 类型上实现标准库的 Display trait,也可在本地的 Vec<T> 上实现 Summary trait,但不能在 aggregator crate 中为标准库的 Vec<T> 实现 Display trait,此限制是一致性属性中的孤儿规则,确保代码互不干扰。

默认实现

有时为 trait 中的部分或全部方法提供默认行为很有用,在实现 trait 时可保留或覆盖。如为 Summary trait 的 summarize 方法指定默认字符串:

代码语言:rust
复制
pub trait Summary {
    fn summarize(&self) -> String {
        String::from("(Read more...)")
    }
}

pub struct NewsArticle {
    pub headline: String,
    pub location: String,
    pub author: String,
    pub content: String,
}

impl Summary for NewsArticle {}

pub struct Tweet {
    pub username: String,
    pub content: String,
    pub reply: bool,
    pub retweet: bool,
}

impl Summary for Tweet {
    fn summarize(&self) -> String {
        format!("{}: {}", self.username, self.content)
    }
}

对 NewsArticle 可使用空 impl 块,仍能调用 summarize 方法。创建默认实现不影响 Tweet 上已有实现,覆盖默认实现的语法与实现无默认的方法相同。默认实现可调用同一 trait 中的其他方法,且无法从同一方法的重写实现中调用默认实现。

特征作为参数

已知如何定义和实现 trait 后,可利用其定义接受多种类型的函数。以下用之前实现的 Summary trait 定义 notify 函数,调用 item 参数的 summarize 方法(item 为实现 Summary trait 的类型):

代码语言:rust
复制
pub trait Summary {
    fn summarize(&self) -> String;
}

pub struct NewsArticle {
    pub headline: String,
    pub location: String,
    pub author: String,
    pub content: String,
}

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

pub struct Tweet {
    pub username: String,
    pub content: String,
    pub reply: bool,
    pub retweet: bool,
}

impl Summary for Tweet {
    fn summarize(&self) -> String {
        format!("{}: {}", self.username, self.content)
    }
}

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

这里 item 参数用 impl 关键字和 trait 名指定,可接受实现 Summary trait 的任何类型,函数体内可调用 Summary trait 中的方法。使用 impl Trait 语法适用于简单情况,使代码简洁,但它是 trait 边界(trait bound)的语法糖。trait 边界语法如下:

代码语言:rust
复制
pub fn notify<T: Summary>(item: &T) {
    println!("Breaking news! {}", item.summarize());
}

此形式更冗长,将 trait 边界与泛型类型参数声明放在冒号后和尖括号内。impl Trait 语法在简单情况方便,复杂情况可用 trait 边界表达。例如有两个实现 Summary 的参数,用 impl Trait 语法可允许不同类型,用 trait 边界可强制相同类型:

代码语言:rust
复制
// impl Trait 允许不同类型
pub fn notify(item1: &impl Summary, item2: &impl Summary) {
    // 函数体
}

// trait 边界强制相同类型
pub fn notify<T: Summary>(item1: &T, item2: &T) {
    // 函数体
}

使用 + 语法指定多个特征边界

可指定多个 trait 边界,如希望 notify 函数对 item 同时使用 display 格式化和 summarize 方法,可用 + 语法:

代码语言:rust
复制
pub fn notify(item: &(impl Summary + Display)) {
    // 函数体
}

泛型类型也适用:

代码语言:rust
复制
pub fn notify<T: Summary + Display>(item: &T) {
    // 函数体
}

指定两个特征边界后,函数体内可调用 summarize 方法并格式化 item。

使用 where 子句使特征界限更清晰

过多 trait 边界会使函数签名难读,Rust 提供在函数签名后的 where 子句中指定 trait 边界的语法,使函数签名更清晰。例如:

代码语言:rust
复制
// 不使用 where 子句
fn some_function<T: Display + Clone, U: Clone + Debug>(t: &T, u: &U) -> i32 {
    // 函数体
}

// 使用 where 子句
fn some_function<T, U>(t: &T, u: &U) -> i32
where
    T: Display + Clone,
    U: Clone + Debug,
{
    // 函数体
}

返回实现特征的类型

可在返回位置用 impl Trait 语法返回实现某 trait 的类型,如:

代码语言:rust
复制
pub trait Summary {
    fn summarize(&self) -> String;
}

pub struct NewsArticle {
    pub headline: String,
    pub location: String,
    pub author: String,
    pub content: String,
}

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

pub struct Tweet {
    pub username: String,
    pub content: String,
    pub reply: bool,
    pub retweet: bool,
}

impl Summary for Tweet {
    fn summarize(&self) -> String {
        format!("{}: {}", self.username, self.content)
    }
}

fn returns_summarizable() -> impl Summary {
    Tweet {
        username: String::from("horse_ebooks"),
        content: String::from("of course, as you probably already know, people"),
        reply: false,
        retweet: false,
    }
}

通过用 impl Summary 作为返回类型,指定函数返回实现 Summary trait 的类型,无需指定具体类型。但只有返回单一类型时才能用 impl Trait,如返回 NewsArticle 或 Tweet 则不行,编译器对此有限制,后续会介绍如何处理这种情况。

使用 trait 边界有条件地实现方法

通过在 impl 块中使用带有泛型类型参数的 trait 边界,可有条件地为实现特定 trait 的类型实现方法。例如:

代码语言:rust
复制
use std::fmt::Display;

struct Pair<T> {
    x: T,
    y: T,
}

impl<T> Pair<T> {
    fn new(x: T, y: T) -> Self {
        Self { x, y }
    }
}

impl<T: Display + PartialOrd> Pair<T> {
    fn cmp_display(&self) {
        if self.x >= self.y {
            println!("The largest member is x = {}", self.x);
        } else {
            println!("The largest member is y = {}", self.y);
        }
    }
}

这里 Pair<T> 总是实现 new 函数,仅当内部类型 T 实现 PartialOrd 和 Display trait 时才实现 cmp_display 方法。也可为实现另一个 trait 的任何类型有条件地实现一个 trait,这种实现称为 blanket implementations(泛型实现),在 Rust 标准库中广泛使用。如标准库在实现 Display trait 的任何类型上实现 ToString trait,可在实现 Display trait 的整数上调用 to_string 方法:

代码语言:rust
复制
fn main() {
    let s = 3.to_string();
}

特性和特性边界使我们能编写使用泛型类型参数的代码减少重复,同时向编译器指定泛型类型行为,编译器在编译时检查,提高性能且不放弃泛型灵活性。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 内容
    • 定义特征
    • 在Type上实现trait
    • 默认实现
    • 特征作为参数
    • 使用 + 语法指定多个特征边界
    • 使用 where 子句使特征界限更清晰
    • 返回实现特征的类型
    • 使用 trait 边界有条件地实现方法
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档