Rust 学习笔记:trait 对象

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

Rust 学习笔记:trait 对象

假设我们创建一个名为 gui 的库 crate。这个 crate 可能包括一些供人们使用的类型,比如 Button 或 TextField。此外,gui 用户希望创建自己的可绘制类型:例如,一个程序员可能会添加 Image,而另一个程序员可能会添加 SelectBox。在编写库时,我们不可能知道并定义其他程序员可能想要创建的所有类型。但是我们知道 gui 需要跟踪许多不同类型的值,它需要对这些不同类型的值调用一个 draw 方法。

要在具有继承的语言中做到这一点,我们可以定义一个名为 Component 的类,并在其上有一个名为 draw 的方法。其他类,如 Button、Image 和 SelectBox,将从 Component 继承,从而继承 draw 方法。它们都可以重写 draw 方法来定义它们的自定义行为,但是框架可以将所有类型视为组件实例并对它们调用 draw。

因为 Rust 没有继承,我们需要另一种方式来构建 gui 库,以允许用户使用新类型扩展它。

定义共同行为的特征

Rust 提供泛型来支持抽象编程,但泛型要求类型在编译期可知。

trait 对象是另一种抽象方式:在运行时支持不同类型的值,前提是它们实现了某种 trait。

我们可以使用 trait 对象来代替泛型或具体类型。无论我们在哪里使用 trait 对象,Rust 的类型系统都会在编译时确保在该上下文中使用的任何值都将实现 trait 对象的 trait。因此,我们不需要在编译时知道所有可能的类型。

在 Rust 中,我们避免将结构体和枚举称为“对象”,以区别于其他语言的对象。在结构体或枚举中,结构体字段中的数据和 impl 块中的行为是分开的,而在其他语言中,组合成一个概念的数据和行为通常被标记为对象。

trait 对象更像其他语言中的对象,因为它们结合了数据和行为。但 trait 对象与传统对象的不同之处在于,我们不能向 trait 对象添加数据。trait 对象不像其他语言中的对象那样普遍有用,它们主要用于在通用行为上实现抽象。

通过指定某种指针(如 & 或 Box<T>),然后指定 dyn 关键字,然后指定相关的 trait,可以创建 trait 对象。

为了实现我们希望 gui 拥有的行为,我们将定义一个 Draw trait,它将有一个名为 draw 的方法。然后我们可以定义一个接受 trait 对象的 Vec。trait 对象既指向实现指定 trait 的类型的实例,也指向用于在运行时查找该类型的 trait 方法的表。

Draw trait 的定义:

pub trait Draw {
    fn draw(&self);
}

定义一个名为 Screen 的结构体,它包含一个名为 components 的 Vec。这个 Vec 的类型是 Box<dyn draw>,它是一个 trait 对象,是实现 Draw trait 的 Box 内任何类型的替代品。

pub struct Screen {
    pub components: Vec<Box<dyn Draw>>,
}

在 Screen 结构体中,我们将定义一个名为 run 的方法,该方法将对它的每个组件调用 draw 方法.

impl Screen {
    pub fn run(&self) {
        for component in self.components.iter() {
            component.draw();
        }
    }
}

这与定义使用带有 trait 约束的泛型类型参数的结构体不同。泛型类型参数一次只能被一个具体类型替代,而 trait 对象允许在运行时为 trait 对象填充多个具体类型。

例如,我们可以使用泛型类型和 trait 约束来定义 Screen 结构体:

pub struct Screen<T: Draw> {
    pub components: Vec<T>,
}

impl<T> Screen<T>
where
    T: Draw,
{
    pub fn run(&self) {
        for component in self.components.iter() {
            component.draw();
        }
    }
}

这将我们限制在一个 Screen 实例中,该实例具有所有类型为 Button 或所有类型为 TextField 的组件列表。如果只使用同构集合,那么使用泛型和 trait 约束是可取的,因为在编译时定义将是单态的,以便使用具体类型。

另一方面,使用 trait 对象的方法,一个 Screen 实例可以保存 Vec<T>,其中包含 Box<Button> 和 Box<TextField>。让我们看看这是如何工作的,然后我们将讨论运行时性能的含义。

实现 trait

现在我们将添加一些实现 Draw trait 的类型。例如 Button 类型,想象一下实现的样子,一个 Button 结构体可能有 width, height 和 label 字段。

pub struct Button {
    pub width: u32,
    pub height: u32,
    pub label: String,
}

impl Draw for Button {
    fn draw(&self) {
        // code to actually draw a button
    }
}

Button 上的宽度、高度和标签字段将不同于其他组件上的字段。

我们想要在屏幕上绘制的每种类型都将实现 Draw trait ,但将在 draw 方法中使用不同的代码来定义如何绘制该特定类型。

例如,在其他库上实现一个具有宽度、高度和选项字段的 SelectBox 结构体,也在 SelectBox 类型上实现 Draw trait。

use gui::Draw;

struct SelectBox {
    width: u32,
    height: u32,
    options: Vec<String>,
}

impl Draw for SelectBox {
    fn draw(&self) {
        // code to actually draw a select box
    }
}

库的用户现在可以编写他们的 main 函数来创建 Screen 实例。对于 Screen 实例,添加一个 SelectBox 和一个 Button,方法是将它们分别放入 Box<T> 中,从而成为一个 trait 对象。然后,它们可以调用 Screen 实例上的 run 方法,该方法将在每个组件上调用 draw。

use gui::{Button, Screen};

fn main() {
    let screen = Screen {
        components: vec![
            Box::new(SelectBox {
                width: 75,
                height: 10,
                options: vec![
                    String::from("Yes"),
                    String::from("Maybe"),
                    String::from("No"),
                ],
            }),
            Box::new(Button {
                width: 50,
                height: 10,
                label: String::from("OK"),
            }),
        ],
    };

    screen.run();
}

run 不需要知道每个组件的具体类型是什么。它不会检查组件是什么类型的实例,它只是调用组件上的 draw 方法。通过指定 Box<dyn Draw> 作为组件 Vec 中值的类型,我们已经定义了 Screen 需要的值,我们可以在这些值上调用 draw 方法。

如果传入了没有实现 Draw trait 的类型(例如 String 类型),会得到编译错误。

use gui::Screen;

fn main() {
    let screen = Screen {
        components: vec![Box::new(String::from("Hi"))],
    };

    screen.run();
}

error[E0277]: the trait bound `String: Draw` is not satisfied

trait 对象执行动态分派

回忆一下编译器对泛型执行的单态过程:编译器为我们用来代替泛型类型参数的每个具体类型生成函数和方法的非泛型实现。由单态生成的代码正在执行静态分派,即编译器在编译时知道你正在调用哪个方法。这与动态分派相反,动态分派是指编译器在编译时无法判断你调用的是哪个方法。在动态分派的情况下,编译器发出的代码将在运行时确定要调用哪个方法。

当我们使用 trait 对象时,Rust 使用动态分派。编译器不知道使用 trait 对象的代码可能使用的所有类型,因此它不知道调用哪个类型上实现的哪个方法。相反,在运行时,Rust 使用 trait 对象内部的指针来知道要调用哪个方法。这种查找会产生静态分派所没有的运行时成本。动态分派还阻止编译器选择内联方法的代码,这反过来又阻止了一些优化。

Rust 有一些规则,称为 dyn 兼容性,关于在哪里可以和不可以使用动态分派。参考:https://doc.rust-lang.org/reference/items/traits.html#dyn-compatibility。

对比:

特性泛型trait 对象
派发方式静态派发(编译期)动态派发(运行时)
性能更高(有内联优化)略低(有运行时开销)
类型统一要求只能是一种类型可以是多种类型
用于异构集合适合不适合
【Rust trait特质】如何在Rust中使用trait特质,全面解析与应用实战 在Rust中,Trait是一种定义共享行为的机制,类似于其他语言中的"接口"(interface)或"抽象类"(abstract class)。 Trait允许我们定义一组方法签名,这些方法可以被不同类型实现,从而实现多态行为。 阅读详情

相关推荐

【Rust 高级trait】Rust trait的一些高级用法解密

Rust trait的一些高级用法解密。 关联类型用法; 默认泛型类型参数和运算符重载; 完全限定语法; trait继承; newtype模式。

景天科技苑 3万+

Rust从入门到实战系列二百三十:newtype 模式用以在外部类型上实现外部 trait

在第十章的 ” 为类型实现 trait” 部分,我们提到了孤儿规则(orphan rule),它说明只要 trait 或类型。Display 的实现使用 self.0 来访问其内部的 Vec,因为 Wrapper 是元组结构体而 Vec 是结构体。如果希望新类型拥有其内部类型的每一个方法,为封装类型实现 Deref trait(第十五章 ” 通过。trait 的类型的简单封装。装类型拥有所有内部类型的方法 —— 比如为了限制封装类型的行为 —— 则必须只自行实现所需的方法。总位于索引 0 的项。

wanmeijuhao的博客 476

零成本实现飞牛 OS 内网穿透:CloudFlare 隧道搭建全攻略

一、引言:突破内网限制的最优解当你的内网环境中部署了飞牛 OS 系统(无论是老旧电脑还是 NAS 设备),想要实现公网访问时,传统方案往往面临两大痛点:公网 IP 申请困难且成本高DDNS 动态解析配置复杂专业内网穿透工具存在付费门槛今天为大家带来零成本解决方案:通过免费域名搭配 CloudFlare 隧道技术,无需公网 IP 即可轻松实现飞牛 OS 服务的公网访问。全程操作可视化,新手也能快速上手!二、前期准备:必备工具清单(1)域名准备。

Honmaple的博客 7491

【Rust自学】19.2. 高级trait:关联类型、默认泛型参数和运算符重载、完全限定语法、supertrait和newtype

我们首先在第10章的10.3. trait Pt.1:trait的定义、约束与实现 和 10.4. trait Pt.2:trait作为参数和返回类型、trait bound 中介绍了trait,但我们没有讨论更高级的细节。现在我们来深入了解

A Rookie in San Domenico School 1633

苏州大学自动化考研842自动控制原理:手把手教你用胡寿松《自控》高效备考(附复试电工/电子/微机原理攻略)

本文详细解析苏州大学自动化考研842自动控制原理备考策略,以胡寿松《自动控制原理》为核心教材,提供三阶段高效学习路径和实战技巧。重点涵盖频域分析、根轨迹绘制等高频考点,并针对机电、光电、轨道三个学院的复试科目(电工电子技术、电子技术基础、微机原理与接口技术)给出差异化备考方案,助力考生全面提升笔试和面试表现。

weixin_42638178的博客 399

@transactional滥用导致数据源连接池耗尽问题

@transactional滥用导致数据源连接池耗尽问题

qq_43205267的博客 1199

【Rust】——高级trait

🎯关联类型在trait定义中指定占位符类型 🎯默认泛型类型参数和运算符重载 🎯完全限定语法与消歧义:调用相同名称的方法 🎯完全限定语法与消歧义:调用相同名称的方法 🎯父trait用于在另一个trait中使用trait的功能 🎯newtype模式用以在外部类型上实现外部trait

shsjssnn的博客 3376

Ubuntu 26.04 本地安装 GitLab CE 完整教程(非 Docker 方式)

本文详细介绍了在Ubuntu 26.04系统上通过Omnibus包安装GitLab CE的完整流程。主要内容包括:环境准备(系统要求、依赖安装)、配置清华镜像源、执行安装命令、修改配置文件、服务管理命令以及常见问题解决方法。文章特别强调了内存要求(建议4GB以上)、首次登录密码获取、HTTPS安全配置等关键点,并提供了性能优化建议和安全设置指导。相比Docker方式,Omnibus安装具有性能更好、管理方便等优势,适合需要私有化部署GitLab的场景。

1369

Rust 使用trait 来实现函数重载

fn main(){ let hero = Hero::with("Apple".to_string()); println!("{:#?}",hero); let hero2 = Hero::with(Sex::Gril); println!("{:#?}",hero2); let hero3 = Hero::with(32); println!("{:#?}",hero3); let hero4 = Hero::new(); println

muyouking 1529

基于Matlab模拟六自由度Stewart并联机器人运动学逆解

在现代工业和科学领域中,机器人技术的发展已经取得了巨大的进步,为生产和研究提供了许多便利。其中,六自由度Stewart并联机器人是一种广泛应用于各种领域的机器人系统。它具有高精度、高刚度和高可靠性的特点,被广泛应用于飞行模拟器、手术机器人和运动仿真等领域。然而,要使Stewart并联机器人能够实现复杂的运动任务,我们需要了解其运动学逆解。运动学逆解是指根据机器人末端执行器的位置和姿态,计算出各个关节的角度和长度。这对于控制机器人的运动非常重要,因为它可以帮助我们确定机器人各个关节的运动范围和工作空间。

qq_59747472的博客 1127

2026 AI浏览器Agent终极对比:BrowserAct、agentBrowser、PP-Browser 谁更适合落地?(安装+实战+场景全覆盖)

1.不要盲目追求最新框架:很多人跟风用agentBrowser,结果业务是多步骤复杂办公流程,反而不如BrowserAct稳定;2.风控场景优先PP-Browser:但凡需要登录账号、频繁访问正规平台,无脑选PP-Browser,能避开99%的封禁问题;3.AI批量解析优先agentBrowser:做知识库爬取、网页内容总结、批量问答,省Token就是省成本,长期使用性价比碾压另外两者;4.个人开发调试优先BrowserAct:容错高、日志全、调试简单,新手上手最快,不容易报错翻车。

十年老程序猿,AI 疯狂研究专家,前沿硬核知识分享 327

Rust中的Trait

类似接口,但和php中的trait又有点不一样。 pub trait Summary { fn summarize(&self) -> String; } pub struct NewArticle { pub headline: String, pub author: String, } pub struct Tweet...

weixin_30379531的博客 430

ROS & ROS2 机器人深度相机激光雷达多传感器标定工具箱

不过,多传感器标定工具箱允许通过编辑 yaml 文件来调整标定目标的参数: <repository>/mutlisensor_calibration/cfg/TargetWithCirclesAndAruco.yaml%YAML:1.0rows: 4cols: 1dt: irows: 4cols: 2dt: frows: 1cols: 12dt: fCAD 模型网格/云通过 GICP 将模型数据与分割点云对齐,用于优化检测到的目标姿态。

2506_90492529的博客 2107

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

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

ProgramNovice的博客 383

slew dreate,slew threshold与transition计算

​只看rise情况下,from pin取slew threshold10%-90%的区间,slew derate为1,计算得到transition为1.002e-10,to pin的slew threshold 为30%-70%,slew derate 0.5,从波形列表上取相近的值3.75e-10和8.75e-10,计算得到transition为(8.75e-10-3.75e-10)/0.5=1e-9,考虑精度和取值区间问题,基本符合工具计算的值,如下:​。如下示例,在.lib库中有定义以下内容。

m0_61544122的博客 793

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

Rust 学习笔记:Trait

Rust 学习笔记:Trait

ProgramNovice的博客 1716

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

【Rust自学】17.2. 使用trait对象来存储不同值的类型

Rust并没有提供继承功能,所以想使用Rust来构建GUI工具就得使用其他方法——为共有行为定义一个trait

A Rookie in San Domenico School 1368

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

Devexpress16.2.6破解工具

Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具

Rust 学习笔记:高级 trait

Rust 学习笔记:高级 trait

ProgramNovice的博客 952

Rust trait 与泛型

使用trait关键字定义特型。提供一个名字,并列出特型方法的类型签名即可。上面描述的特型都是独立的:特型是类型可以实现的一组方法。特型也可以用于多种类型必须协同工作的场景中。它们可以描述多个类型之间的关系。特型会为每个迭代器类型与其生成的值的类型建立联系。特型与可以相乘的类型有关。在表达式a * b中,值 a 和 b 可以是相同类型,也可是不同类型。randcrate 中包含随机数生成器的特型(rand::Rng)和可被随机生成的类型的特型。

编程回响 1682

【Rust】——估计不同类型值的trait对象

🎯定义通用行为的trait 🎯实现trait 🎯trait对象执行分发

shsjssnn的博客 1069

Rust Trait Object 和对象安全

所谓 trait 对象,可以理解为其它面向对象语言中,指向接口或基类的指针或引用。其它OO语言指向基类的指针在运行时确定其实际类型。 Rust 没有类继承,指向 trait 的指针或引用起到类似的效果,运行时被确定具体类型。所以编译期间不知道大小。 trait 对象体现出来就是对一个 trait类型的指针或引用。 不是所有的 trait都可以被当做 trait对象使用,能作为 Trait 对象使用的 trait 要同时满足以下规则: rule 1: trait 的 Self 类型参数不能..

萧猛的技术博客 3079

Rust trait系统深度解析:实现多态的利器

最近在学习 Rust 的过程中,被 trait 系统的强大功能深深吸引。作为一个从后端转 Rust 的萌新,我之前对接口的理解主要来自 Java 和 Python,但是 Rust 的 trait 系统给了我全新的认识。它不仅提供了类似接口的功能,还支持默认实现、泛型约束、 trait 对象等高级特性,让代码更加灵活和可复用。今天就来分享一下我学习 Rust trait 系统的心得,希望能帮到和我一样的萌新们。在 Rust 中,trait 是一种定义方法集的方式,类似于其他语言中的接口(interface)

no1coder的博客 1万+

rust 面向对象、trait

给结构体添加方法有2种,一种是直接实现,一种的带trait的实现。直接实现的方式中,数据和方法是对应的。带trait的实现中,trait是一组可以被共享的行为,只要实现了特征,你就能使用这组行为。

nameofcsdn的博客 1668

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

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

ProgramNovice的博客 1350

Rust 学习笔记:Deref trait

Rust 学习笔记:Deref trait

ProgramNovice的博客 1010

dyn-clone:让 Rust 动态 trait 对象实现克隆的终极解决方案

在 Rust 开发中,动态 trait 对象(trait object)的克隆一直是开发者面临的痛点。标准库的 `Clone` trait 由于其 Self 类型限制,无法直接用于 trait 对象。而 **dyn-clone** 库正是为解决这一问题而生,它提供了一种简单高效的方式,让你的 trait 对象也能轻松实现克隆功能。 ## 为什么需要 dyn-clone? Rust 的类型系统虽

gitblog_00220的博客 1020
上一篇: Rust 学习笔记:关于异步 trait 的练习题
下一篇: Rust 学习笔记:面向对象语言的特点
UestcXiye
博客等级 码龄8年 9028粉丝 1306原创
评论
成就一亿技术人!
拼手气红包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、付费专栏及课程。

余额充值