Rust 学习笔记:Trait

rust笔记13:trait对象 在 Rust 中,trait 对象 是一种实现动态分发(dynamic dispatch)的机制,允许你在运行时处理实现了某个 trait 的不同类型。Trait 对象是 Rust 中实现多态性的重要工具之一,特别是在需要处理多种类型但又不确定具体类型的情况下。 阅读详情

Rust 学习笔记:Trait

trait 定义了特定类型具有的功能。

我们可以使用 trait 以抽象的方式定义共享行为。

我们可以使用 trait bounds 来指定哪些类型才是我们想要的泛型类型。

trait 类似于在其他语言中称为接口的特性,尽管存在一些差异。

定义 trait

类型的行为由可以调用该类型的方法组成。

如果我们能在不同的类型上调用相同的方法,就意味着这些不同的类型共享相同的行为。

Trait 定义:将不同的方法签名汇成一个方法签名,由此定义一套共享的行为。

假设我们有多个结构体保存不同类型和数量的文本:

  • NewsArticle 结构体:新闻报道
  • Tweet:推文

创建一个名为 aggregator 的库 crate,它可以显示可能存储在 NewsArticle 或 Tweet 实例中的数据摘要。

在这里插入图片描述

我们将通过在实例上调用 summary 方法来请求该摘要。

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

在这里,我们使用 trait 关键字和 trait 的名称来声明 trait,在本例中是 Summary。我们还将这个 trait 声明为 pub,这样依赖于这个 crate 的 crate 也可以使用这个 trait。在花括号内,我们声明了方法签名。我们没有在花括号内提供实现,而是使用分号。实现此特性的每个类型都必须为方法体提供自己的自定义行为。编译器将强制任何具有 Summary trait 的类型,其方法将使用此签名精确定义。

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

在类型上实现 trait

首先给出两个结构体的定义:

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

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

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

既然我们已经定义了 Summary trait 方法所需的签名,我们就可以实现它。

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

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

在类型上实现 trait 的过程:在 impl 之后,我们输入要实现的 trait 名,然后使用 for 关键字,然后指定要为之实现 trait 的类型名。在 impl 块中,我们放置 trait 定义所定义的方法签名,并在方法体中填充我们希望 trait 的方法针对特定类型具有的特定行为。

用户可以像调用常规方法一样调用 NewsArticle 和 Tweet 实例上的 trait 方法。唯一的区别是,用户必须将 trait 和类型一起纳入作用域。

新建一个 main.rs:

use aggregator::{Tweet, Summary};

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

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

因为我们创建的是一个库 crate,默认执行的是 Test,只能运行一些脚本化测试。

在这里插入图片描述

现在我们想运行 main.rs 中的代码,我们可以点击 RustRover 右侧的 Cargo 按钮,点击“运行 Cargo 命令”,输入“cargo run”即可。

在这里插入图片描述

在这里插入图片描述

添加后,配置里也会多一个 run,以后用这个就行。

在这里插入图片描述

这个例子打印:1 new tweet: horse_ebooks: of course, as you probably already know, people.

其他依赖于 aggregator 的 crate 也可以将 Summary trait 引入作用域,从而在它们自己的类型上实现 Summary。

需要注意的一个限制是,只有当 trait 或类型或者两者都是本地的时候,我们才能在一个类型上实现 trait。例如,我们可以在像 Tweet 这样的自定义类型上实现像 Display 这样的标准库特性,作为我们的 aggregator crate 功能的一部分,因为 Tweet 类型是我们自定义的类型。我们也可以在 aggregator crate 中的 Vec<T> 上实现 Summary,因为 Summary trait 是我们自定义的 trait。

我们不能在外部类型上实现外部特征。例如,我们不能在 aggregator crate 中的 Vec<T> 上实现 Display trait,因为 Display 和 Vec<T> 都是在标准库中定义的。

这种限制被称为“孤儿规则”,它要求 trait 或类型必须属于当前 crate。这条规则确保其他人的代码不会破坏你的代码,反之亦然。如果没有这个规则,两个 crate 可以为相同的类型实现相同的 trait, Rust 就不知道该使用哪个实现。

默认实现

我们可以为 trait 中的一些或所有方法设置默认行为。

当我们在特定类型上实现 trait 时,我们可以保留或覆盖每个方法的默认行为。

修改 Summary trait 的 Summary 方法,指定一个默认字符串。

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

修改代码,为 NewsArticle {} 指定一个空的 impl 块,其中包含 impl Summary。

impl Summary for NewsArticle {

}

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

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

默认实现可以调用同一 trait 中的其他方法,即使这些其他方法没有默认实现。

例如,我们可以定义 Summary trait,让它有一个 summarize_author 方法,它的实现是必需的。然后定义一个 summarize 方法,它的默认实现调用 summarize_author 方法:

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

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

要使用这个版本的 Summary trait,只需要在对类型实现 trait 时定义 summarize_author:

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

定义 summarize_author 之后,我们可以在 Tweet 结构体的实例上调用 summarize,而 summarize 的默认实现将调用我们提供的 summarize_author 的定义。

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

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

程序打印:1 new tweet: (Read more from @horse_ebooks…)

请注意,不可能从同一方法的重写实现中调用默认实现。

作为参数的 trait

使用 trait 可以定义接受许多不同类型的函数。

我们使用在 NewsArticle 和 Tweet 类型上实现的 Summary trait 来定义一个通知函数,该函数在其 item 参数上调用 Summary 方法,该参数是实现 Summary trait 的某种类型。要做到这一点,我们使用 impl Trait 语法。

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

我们没有为 item 参数指定具体类型,而是指定了 impl 关键字和 trait 名称。此参数接受实现指定 trait 的任何类型。在 notify 的主体中,我们可以对 item 调用来自 Summary trait 的任何方法。我们可以调用 notify 并传入 NewsArticle 或 Tweet 的任何实例。使用任何其他类型(如 String)调用该函数的代码将无法编译,因为这些类型不实现 Summary。

trait bound 语法

impl trait 语法适用于简单的情况,但实际上是一种更长的语法糖,称为 trait 绑定。

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

我们将 trait 约束泛型类型参数的声明放在冒号之后和尖括号内。

impl trait 语法很方便,在简单的情况下可以编写更简洁的代码,而更完整的 trait 绑定语法在其他情况下可以表达更复杂的代码。

pub fn notify(item1: &impl Summary, item2: &impl Summary) {

如果我们希望这个函数允许 item1 和 item2 具有不同的类型(只要这两个类型都实现了 Summary),那么使用 impl trait 是合适的。但是,如果我们想强制两个形参具有相同的类型,就必须使用 trait 绑定。

pub fn notify<T: Summary>(item1: &T, item2: &T) {

作为 item1 和 item2 形参类型指定的泛型类型 T 约束了函数,使得作为 item1 和 item2 实参传递的值的具体类型必须相同。

用 + 语法指定多个 trait 约束

我们还可以指定多个 trait 绑定。

例如,我们在函数 notify 的定义中指定项必须同时实现 Display 和 Summary。我们可以使用 + 连接 trait。

pub fn notify(item: &(impl Summary + Display)) {

+ 语法也适用于泛型类型的 trait 边界。

pub fn notify<T: Summary + Display>(item: &T) {

有了指定的两个 trait 边界,notify 的主体就可以调用 Summary 并使用 {} 来格式化 item。

使用 where 子句使 trait 边界更清晰

使用太多的 trait 边界使得具有多个泛型类型参数的函数可能在函数名和参数列表之间包含大量的 trait 绑定信息,让函数签名难以阅读。

由于这个原因,Rust 在函数签名后的 where 子句中指定 trait 边界。

// 这种函数签名太长了
fn some_function<T: Display + Clone, U: Clone + Debug>(t: &T, u: &U) -> i32 {
👇 👇 👇
// 这样就清晰多了
fn some_function<T, U>(t: &T, u: &U) -> i32
where
    T: Display + Clone,
    U: Clone + Debug,
{

返回实现 trait 的类型

我们也可以在返回位置使用 impl trait 语法来返回实现 trait 的某种类型的值,如下所示:

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

通过对返回类型使用 impl Summary,我们指定 returns_summarizable 函数返回实现 Summary trait 的某种类型,而不指定具体类型。在本例中,returns_summarizable 返回一个 Tweet,但是调用这个函数的代码不需要知道这个。

仅通过它实现的 trait 指定返回类型的能力在闭包和迭代器上下文中特别有用。闭包和迭代器创建的类型只有编译器知道,或者需要很长时间才能指定。impl trait 语法允许指定一个函数返回实现 Iterator trait 的某种类型,而不需要写出很长的类型。

但是,编译器只有在返回单个类型时才能使用 impl trait 语法。

例如,这段代码返回一个 NewsArticle 或一个 Tweet,返回类型指定为 impl Summary,这是行不通的:

fn returns_summarizable(switch: bool) -> impl Summary {
    if switch {
        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.",
            ),
        }
    } else {
        Tweet {
            username: String::from("horse_ebooks"),
            content: String::from(
                "of course, as you probably already know, people",
            ),
            reply: false,
            repost: false,
        }
    }
}

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

通过使用与使用泛型类型参数的 impl 块绑定的 trait,我们可以为实现指定 trait 的类型有条件地实现方法。

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> 对任意的内部类型 T 都实现了 new 方法。但 Pair<T> 只有在其内部类型 T 实现了支持比较的 PartialOrd trait 和支持打印的 Display trait 时才实现 cmp_display 方法。

我们还可以为任何实现了另一个 trait 的类型有条件地实现一个 trait。

例如,标准库在任何实现 Display trait 的类型上实现 ToString trait。标准库中的 impl 块看起来类似于以下代码:

impl<T: Display> ToString for T {
    // --snip--
}

因为标准库有这种覆盖实现,所以我们可以在任何实现 Display trait 的类型上调用 ToString trait 定义的 to_string 方法。例如,我们可以像这样将整数转换为相应的 String 值,因为整数实现了 Display trait:

let s = 3.to_string();

trait 和 trait 边界让我们可以编写使用泛型类型参数来减少重复的代码,但也可以向编译器指定我们希望泛型类型具有特定的行为。然后,编译器可以使用 trait 绑定信息来检查代码中使用的所有具体类型是否提供了正确的行为。

在动态类型语言中,如果在没有定义方法的类型上调用方法,将在运行时得到错误。但是 Rust 将这些错误转移到编译时,因此我们被迫在代码能够运行之前修复问题。

此外,我们不必编写在运行时检查行为的代码,因为我们已经在编译时检查过了。这样做可以提高性能,而不必放弃泛型的灵活性。

Bootloader Design of PIC18 series MCU 注意:下列描述是在PIC单片机上启用Bootloader的一个相关知识的近似完备集。所有需要了解的,与bootloader与用户态程序交互的理论知识都已给出。包含内存组织,跳转,中断等必备功能的描述。 阅读详情

相关推荐

基于Llama 3搭建中文版(Llama3-Chinese-Chat)大模型对话聊天机器人

前面两篇博文,我们在笔记本部署了Llama 3 8B大模型,使用Ollama搭建了基于Web 可视化对话机器人。在使用过程中,大模型经常出现中文问题英文回答的问题。本博文就用Llama3-Chinese-Chat微调模型来解决这个问题……

老牛啊 9238

Rust特征(Trait)

如果不同的类型具有相同的行为,那么我们就可以定义一个特征,然后为这些类型实现该特征。定义特征是把一些方法组合在一起,目的是定义一个实现某些目标所必需的行为的集合。例如,我们现在有圆形和长方形两个结构体,它们都可以拥有周长,面积。因此我们可以定义被共享的行为,只要实现了特征就可以使用。// 为几何图形定义名为Figure的特征 fn girth(& self) -> u64;// 计算周长 fn area(& self) -> u64;// 计算面积 }

zy010101博客 2283

配置oem 13c以监控管理数据库

使用oem13c监控数据库

雅冰石的专栏 1808

Rust学习笔记007:Trait --- Rust的“接口”

定义trait,实现trait,#[derive(Debug)]的相关实现

ResumeProject的博客 2493

FPGA串口通信----uart(一)

通过PLL锁相环IP的使用,可以输入时钟进行分频,倍频,也可以产生多路时钟输出信号。外部提供晶振的原因:①Fpga电路大多数需要时序分析②晶振在Fpga内部占空间大,所以放置于外部PCB板上Uart异步传输速率不能很快的原因:考虑到工程实际问题,假如时钟周期大(传输速率慢的话),采取到数据电平变化的边沿时刻的概率会小很多,这样数据也很准确,通常uart适用于2M.

mvpkuku的博客 969

零基础速成simulink代码生成——stateflow基础3

此专栏面向嵌入式工程没有matlab开发经验的人,可以快速学习和实践这门技术。MDB技术,越来越成熟和普遍,从手写代码转到基于模型开发已经是大势,不仅减少开发周期,还增加代码的复用性。汽车领域、医疗和算法建模广泛使用。

不断debug,优化中…… 1711

弄懂Rust编程中的Trait

trait 定义了某个特定类型拥有可能与其他类型共享的功能。可以通过 trait 以一种抽象的方式定义共享的行为。可以使用 trait bounds 指定泛型是任何拥有特定行为的类型。

这里是二进制空间安全博客,主要分享网络安全、信息安全和数据安全相关的技术文章。内容覆盖编程语言、基础知识、漏洞分析、攻防技巧、安全工具使用、最佳实践等安全技术主题。 1389

安卓手机强制清理数据恢复出厂设置后无法设置密码怎么办?(分享亲身经历的有效解决方案)

📱安卓手机强制清数据恢复出厂设置后,竟无法设置锁屏密码!本文将解析问题根源,分享亲测有效的解决方案,帮你快速摆脱困扰🎉

XiaoqiangClub的博客 241

Rust:Trait 详解

Rust语言里不同的type(比如 struct, enum等)可以调用的函数主要包括本身实现的方法。此外,Rust也提供了trait来定义不同type所需的“common behavior”,以此简化代码。一个trait里面可以包含多个method 对type实现trait: 以strcut为例,对struct实现trait,方式与实现struct方法类似。实现时可以选择 重写trait中的method,也可以选择不重写,直接使用trait中默认的method定义 结果: 实例化struct,

weixin_52617269的博客 2691

AI Agent Harness实时监控大屏搭建实战

本文将以目前主流的AI Agent管控框架Harness为基础,结合React、ECharts、WebSocket技术,手把手带你搭建一套完整的实时监控大屏,覆盖全局概览、状态分布、工具调用统计、异常告警、实时日志等核心功能,不需要复杂的底层开发,复制代码就能快速跑通。本文我们从需求出发,先梳理了AI Agent的核心监控指标,然后搭建了监控大屏的静态布局,逐个实现了各个图表,对接了Harness的实时数据源,最后加了交互和告警功能,完整走了一遍监控大屏从设计到落地的全流程。

大数据洞察的博客 174

【面试系列】软件架构师 高频面试题及详细解答

本文介绍了软件架构师岗位的初级、中级和高级面试题及详细解答,涵盖了系统设计、面向对象设计、分布式系统、云计算技术、性能优化等关键领域。通过这些问题和解答,候选人可以深入理解软件架构师的核心职责和技能要求。此外,文章总结了在面试中需要掌握的常考知识点,包括架构模式、分布式系统管理、安全性、数据管理和DevOps实践。通过掌握这些知识点,软件架构师能够有效设计和指导复杂系统的结构,确保系统的可扩展性、性能和维护性。

在路上的专栏 850

Rust 学习笔记:Drop trait

Rust 学习笔记:Drop trait

ProgramNovice的博客 535

怀旧版电脑GBC模拟器

再现原版GBC掌机的界面,电脑运行连游戏界面都一样大小,很怀旧,很真实,(英文版)强烈推荐,很小的文件,很难才找到的。

Rust 学习笔记:高级 trait

Rust 学习笔记:高级 trait

ProgramNovice的博客 952

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

Rust 学习笔记:trait 对象

Rust 学习笔记:trait 对象

ProgramNovice的博客 1348

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

Rust 学习笔记:Deref trait

Rust 学习笔记:Deref trait

ProgramNovice的博客 1010

Rust 学习笔记:深入研究异步 trait

Rust 学习笔记:深入研究异步 trait

ProgramNovice的博客 1350

Rust 学习笔记:Future trait 和 Async 语法

Rust 学习笔记:Future trait 和 Async 语法

ProgramNovice的博客 1459

Rust学习笔记:小试一下trait的功能

趁今天是28日,也来学习一下Rust。Rust和Go的设计并不像C#那样面向对象。有点新型现代语言的味道。一般用在系统建设,驱动,物联网,还可以输出Wasm等不错的功能提供前端使用。由于学习力度大,现在比较放松地写,也没有KPI的压力。和python写起来感觉不一样。在开始接触的时候,确实很容易造成编译报错。目前前端界里面除了nodejs,go,rust 正影响js的构建工程,我看有必要还是要接触一下。 1.小试一下trait的功能 下面写一下今晚的练习,接触trait 的时候,会有点像接口,一个方法的集合

夏天的树人的专栏 4031

Rust 学习笔记:关于 Trait 的练习题

Rust 学习笔记:关于 Trait 的练习题

ProgramNovice的博客 277

Rust 学习笔记:关于高级 trait 的练习题

Rust 学习笔记:关于高级 trait 的练习题

ProgramNovice的博客 323

Rust 学习笔记:关于异步 trait 的练习题

Rust 学习笔记:关于异步 trait 的练习题

ProgramNovice的博客 372

corutine rust_Rust学习笔记#5:函数和trait

函数基本语法Rust的函数使用fn关键字开头,函数可以有一系列的输入参数,还有一个返回类型。函数返回可以使用return语句,可以使用表达式。下面是一个标准函数的示例,add函数接受两个i32的参数,然后计算它们的和并返回:fn add(a: i32, b: i32) -> i32 {a + b}println!("{}", add(1, 2)); // 输出:3函数返回值如果不显示标明,...

weixin_39998462的博客 455

Rust 学习笔记:关于 OOP 和 trait 对象的练习题

Rust 学习笔记:关于 OOP 和 trait 对象的练习题

ProgramNovice的博客 383

Rust学习笔记:泛型与Trait——写出可复用的Rust代码

本文系统讲解 Rust 泛型与 Trait 机制,展示如何用泛型实现类型无关代码(如 largest 函数),通过 impl 和 derive 实现类型行为复用与自动代码生成。对比 C 的 void* 与虚函数表,强调 Rust 编译期单态化带来的零运行时开销优势。掌握泛型与 Trait 后,即可编写安全、高效、可复用的 Rust 代码,为模块化开发奠定基础。

欢迎访问我的个人博客:https://www.lxpavilion.top/ 47

Rust 学习笔记:通过 Send 和 Sync trait 实现可扩展并发性

Rust 学习笔记:通过 Send 和 Sync trait 实现可扩展并发性

ProgramNovice的博客 972

Rust 学习笔记:关于 Future trait 和 Async 语法的练习题

Rust 学习笔记:关于 Future trait 和 Async 语法的练习题

ProgramNovice的博客 625
上一篇: Rust 学习笔记:关于泛型的练习题
下一篇: Rust 学习笔记:关于 Trait 的练习题
UestcXiye
博客等级 码龄8年 9028粉丝 1306原创
评论 3
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

UestcXiye

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值