Loading

特性:定义共享行为

特性:定义共享行为
特性定义了特定类型具有并可以与其他类型共享的功能。我们可以使用特性以抽象的方式定义共享行为。我们可以使用特性边界来指定泛型类型可以是任何具有特定行为的类型。
注意:特性类似于其他语言中通常称为接口的功能,尽管存在一些差异。

定义特性
一个类型的行为包括我们可以对该类型调用的方法。如果我们可以对所有这些类型调用相同的方法,那么不同的类型就共享相同的行为。特性定义是一种将方法签名分组以定义完成某些目的所需的一组行为的方式。

例如,假设我们有多个结构体保存不同类型和数量的文本:一个保存特定位置新闻报道的 NewsArticle 结构体,以及一个最多包含 280 个字符并带有元数据(指示它是新推文、转推还是回复另一推文)的 Tweet 结构体。

我们希望创建一个名为 aggregator 的媒体聚合库,该库可以显示可能存储在 NewsArticleTweet 实例中的数据摘要。为此,我们需要每种类型的摘要,并通过调用实例上的 summarize 方法请求该摘要。下面是一个定义公共 Summary 特性的示例,该特性表达了这种行为。

文件名:src/lib.rs

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

示例 10-12:包含 summarize 方法提供的行为的 Summary 特性

在这里,我们使用 trait 关键字和特性的名称(在本例中为 Summary)声明一个特性。我们还将该特性声明为 pub,以便依赖此库的其他库也可以使用此特性,如我们将在一些示例中看到的那样。在花括号内,我们声明描述实现此特性的类型的行为的方法签名,在本例中为 fn summarize(&self) -> String。在方法签名后,我们使用分号而不是提供在花括号内的实现。每个实现此特性的类型必须为方法的主体提供自己的自定义行为。编译器将强制任何具有 Summary 特性的类型必须精确地定义具有此签名的 summarize 方法。

特性可以在其主体中包含多个方法:方法签名每行一个,每行以分号结尾。

在类型上实现特性
现在我们已经定义了 Summary 特性的方法的所需签名,我们可以在我们的媒体聚合器中实现它们。下面是一个在 NewsArticle 结构体上实现 Summary 特性的示例,它使用标题、作者和位置创建 summarize 的返回值。对于 Tweet 结构体,我们将 summarize 定义为用户名后跟整个推文内容,假设推文内容已限制为 280 个字符。

文件名:src/lib.rs

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)
    }
}

示例 10-13:在 NewsArticleTweet 类型上实现 Summary 特性

在类型上实现特性类似于实现常规方法。不同之处在于,在 impl 后,我们放置要实现的特性名称,然后使用 for 关键字,并指定要为其实现特性的类型名称。在 impl 块内,我们放置特性定义定义的方法签名。我们不在每个签名后添加分号,而是使用花括号并用特定类型的方法体填写特性的特定行为。

现在库已经在 NewsArticleTweet 上实现了 Summary 特性,库的用户可以像调用常规方法一样调用这些类型的实例上的特性方法。唯一的区别是用户必须将特性和类型引入作用域。下面是一个二进制库如何使用我们的 aggregator 库的示例:

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());
}

此代码打印 1 new tweet: horse_ebooks: of course, as you probably already know, people.

其他依赖 aggregator 库的库也可以将 Summary 特性引入作用域,以在其自己的类型上实现 Summary。需要注意的一个限制是,我们只能在本地库中实现特性或类型中的至少一个。例如,我们可以在自定义类型 Tweet 上实现标准库特性 Display,因为类型 Tweet 是本地库的一部分。我们还可以在本地库中实现 Summary 特性,因为特性 Summary 是本地库的一部分。

但是,我们不能在外部类型上实现外部特性。例如,我们不能在 Vec<T> 上实现 Display 特性,因为 DisplayVec<T> 都在标准库中定义,不属于本地库。这种限制是被称为一致性(coherence)属性的一部分,具体来说是孤儿规则(orphan rule),因为父类型不存在。该规则确保其他人的代码不会破坏你的代码,反之亦然。如果没有该规则,两个库可能会为同一类型实现相同的特性,而 Rust 不知道该使用哪个实现。

默认实现
有时,为某些或所有方法提供默认行为而不是要求每种类型都实现所有方法是有用的。当我们在特定类型上实现特性时,我们可以保留或覆盖每个方法的默认行为。

在下面的示例中,我们为 Summary 特性的 summarize 方法指定了默认字符串,而不是只定义方法签名。

文件名:src/lib.rs

pub trait Summary {
    fn summarize(&self) -> String {
        String::from("(Read more...)")
    }
}

示例 10-14:定义 Summary 特性并提供 summarize 方法的默认实现

要使用默认实现来总结 NewsArticle 实例,我们指定一个空的 implimpl Summary for NewsArticle {}

尽管我们不再直接定义 NewsArticle 上的 summarize 方法,但我们提供了一个默认实现并指定 NewsArticle 实现了 Summary 特性。因此,我们仍然可以像这样在 NewsArticle 实例上调用 summarize 方法:

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.",
    ),
};

println!("New article available! {}", article.summarize());

此代码打印 New article available! (Read more...)

创建默认实现不需要我们更改 TweetSummary 的实现,因为覆盖默认实现的语法与实现没有默认实现的特性方法的语法相同。

默认实现可以调用特性中的其他方法,即使这些其他方法没有默认实现。这样,特性可以提供很多有用的功能,只需要实现者指定其中的一小部分。例如,我们可以定义 Summary 特性以包含 summarize_author 方法,并要求其实现,然后定义一个默认调用 summarize_author 方法的 summarize 方法:

pub trait Summary {
    fn summarize_author(&self) -> String;

    fn summarize(&self) -> String {
        format!("(Read more from {}...)", self.summarize_author())
    }
}

要使用此版本的 Summary,我们只需在实现特性时定义 summarize_author

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

在定义 summarize_author 后,我们可以在 Tweet 结构体的实例上调用 summarize,而 summarize 的默认实现将调用我们提供的 summarize_author 的定义。因为我们已经实现了 summarize_author,所以 Summary 特性为我们提供了 summarize 方法的行为,而无需编写更多代码。

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());

此代码打印 1 new tweet: (Read more from @horse_ebooks...)

特性作为参数
现在我们可以在 Tweet 实例上调用 summarize,并为 NewsArticle 实例保留默认实现。

我们可以定义函数以接受特性作为参数。我们可以使用 impl Trait 语法来指定参数必须实现特性。这是一个接受任何实现 Summary 特性的类型作为参数的 notify 函数的示例:

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

这个 notify 函数调用传递给它的任何实现 Summary 特性的实例的 summarize 方法。

使用 impl Trait 的代码更加简洁,但 impl Trait 语法在一些复杂的场景下可能不够灵活。在那些情况下,你可以使用带有特性边界的泛型函数。

使用特性边界的泛型函数
notify 函数中,我们可以使用泛型 T 来代替 impl Summary,并指定 T 必须实现 Summary 特性:

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

这种语法提供了更多的灵活性,因为它允许我们对泛型类型进行多重特性约束,并在更复杂的上下文中使用特性边界。

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

这个 notify 函数接受任何实现了 SummaryDisplay 特性的类型。

在特性中使用 Self 类型
我们可以在特性中使用 Self 类型来引用实现该特性的类型。这允许我们在特性方法的签名中使用实现该特性的具体类型。以下是一个在特性中使用 Self 类型的示例:

pub trait Pilot {
    fn fly(&self);
}

pub trait Wizard {
    fn fly(&self);
}

pub struct Human;

impl Pilot for Human {
    fn fly(&self) {
        println!("This is your captain speaking.");
    }
}

impl Wizard for Human {
    fn fly(&self) {
        println!("Up!");
    }
}

impl Human {
    pub fn fly(&self) {
        println!("*waving arms furiously*");
    }
}

let person = Human;
Pilot::fly(&person);
Wizard::fly(&person);
person.fly();

这个示例中,Human 结构体实现了 PilotWizard 特性,以及 Human 自己的 fly 方法。我们可以使用特性名来调用特定的 fly 方法。

通过这些示例,你可以理解 Rust 中的特性如何定义共享行为,并如何在类型上实现这些特性。特性允许我们定义清晰且抽象的接口,并在不同类型之间共享行为。这对于构建模块化和可扩展的代码库非常有用。

在 Rust 中,特性(traits)定义了共享行为,是一种抽象接口,用于指定类型必须实现的方法。特性类似于其他语言中的接口。以下是特性的关键点:

  1. 定义特性:使用 trait 关键字定义特性,并在特性中声明方法签名。实现特性时,必须为这些方法提供具体实现。

  2. 在类型上实现特性:使用 impl 关键字为特定类型实现特性。可以在不同类型上实现同一特性,从而共享行为。

  3. 默认实现:特性可以提供方法的默认实现,类型可以选择使用默认实现或覆盖它。

  4. 特性作为参数:可以定义接受特性作为参数的函数,使用 impl Trait 语法或泛型约束来实现。

  5. 特性边界:通过泛型函数和特性边界,指定泛型类型必须实现特定特性。特性边界允许对泛型类型进行更复杂的约束。

  6. 在特性中使用 Self:在特性中使用 Self 类型,引用实现特性的具体类型,从而允许在特性方法中使用实现类型。

特性是 Rust 中强大的抽象工具,使代码模块化、可扩展,支持多态和接口共享。

以下是如何使用带有特性约束的泛型函数的详细示例和解释:

示例代码

use std::fmt::{self, Display, Formatter};

// 定义 Summary 特性
pub trait Summary {
    fn summarize(&self) -> String;
}

// 实现 Summary 特性的结构体 NewsArticle
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)
    }
}

// 实现 Display 特性的 NewsArticle
impl Display for NewsArticle {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        write!(f, "{}", self.summarize())
    }
}

// 实现 Summary 特性的结构体 Tweet
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)
    }
}

// 实现 Display 特性的 Tweet
impl Display for Tweet {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        write!(f, "{}", self.summarize())
    }
}

// 带有特性约束的泛型函数 notify
pub fn notify<T: Summary + Display>(item: &T) {
    println!("Breaking news! {}", item);
}

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,
    };

    notify(&article);
    notify(&tweet);
}

解释

  1. 定义 Summary 特性

    • 我们定义了一个名为 Summary 的特性,其中包含一个方法 summarize,该方法返回一个 String 类型。
  2. 定义和实现 NewsArticle 结构体

    • NewsArticle 结构体包含 headlinelocationauthorcontent 字段。
    • 实现了 Summary 特性,summarize 方法返回新闻文章的简短描述。
    • 为了使 NewsArticle 可以作为 notify 函数的参数,我们还实现了 Display 特性,其 fmt 方法简单调用 summarize 方法。
  3. 定义和实现 Tweet 结构体

    • Tweet 结构体包含 usernamecontentreplyretweet 字段。
    • 实现了 Summary 特性,summarize 方法返回推文的简短描述。
    • 同样,我们为 Tweet 实现了 Display 特性,其 fmt 方法简单调用 summarize 方法。
  4. 定义带有特性约束的泛型函数 notify

    • notify 函数使用泛型参数 T,并约束 T 必须实现 SummaryDisplay 特性。这样,notify 函数可以接收任何实现了这两个特性的类型。
    • 在函数体中,我们调用 println! 宏来打印 "Breaking news!" 以及传入参数的字符串表示。由于 T 实现了 Display 特性,传入参数会自动调用 fmt 方法进行格式化。
  5. main 函数中使用 notify

    • 创建了 NewsArticleTweet 的实例。
    • 调用了 notify 函数,并传入 NewsArticleTweet 的引用。

输出

运行上述代码后,输出将是:

Breaking news! Penguins win the Stanley Cup Championship!, by Iceburgh (Pittsburgh, PA, USA)
Breaking news! horse_ebooks: of course, as you probably already know, people

这个示例展示了如何定义和实现特性,并如何使用带有特性约束的泛型函数来处理实现了这些特性的不同类型。通过这种方式,我们可以编写更加通用和灵活的代码。

posted @ 2024-06-22 19:44  .net's  阅读(19)  评论(0编辑  收藏  举报