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

news2025/2/25 11:41:46

喜欢的话别忘了点赞、收藏加关注哦(加关注即可阅读全文),对接下来的教程有兴趣的可以关注专栏。谢谢喵!(=・ω・=)
请添加图片描述

17.2.1. 需求

这篇文章以一个例子来介绍如何在Rust中使用trait对象来存储不同值的类型。

在第 8 章中,我们提到Vector的一个限制是它们只能存储一种类型的元素。我们在 8.2. Vector + Enum的应用 中创建了一个解决方法,其中定义了一个SpreadsheetCell枚举,它具有保存整数、浮点数和文本的变体。这意味着我们可以在每个单元格中存储不同类型的数据,并且仍然有一个代表一行单元格的向量。当我们的可互换项是我们在编译代码时知道的一组固定类型时,这是一个非常好的解决方案。

代码如下:

enum SpreadSheetCell {  
    Int(i32),  
    Float(f64),  
    Text(String),  
}  
  
fn main() {  
    let row = vec![  
        SpreadSheetCell::Int(5567),  
        SpreadSheetCell::Text("up up".to_string()),  
        SpreadSheetCell::Float(114.514),  
    ];  
}

然而,有时我们希望我们的库用户能够扩展在特定情况下有效的类型集合,以下是这个例子的需求:

创建一个GUI工具,它会遍历某个元素的列表,依次调用元素的draw方法进行绘制(例如:ButtonTextField等元素)。

这样的需求在面向对象语言里(比如Java或C#)可以定义一个Component父类,里面定义了draw方法。接下来定义ButtonTextField等类,继承于Component这个父类。

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

17.2.2. 为共有行为定义一个trait

首先澄清一些定义:在Rust里我们避免将structenum称为对象,因为它们与impl块是分开的。而trait对象有点类似于其他语言中的对象,因为它们某种程度上组合了数据与行为。

trait对象与传统对象也有不同之处,比如我们无法为trait对象添加数据。

trait对象被专门用于抽象某些共有行为,它没有其他语言中的对象那么通用。

这个GUI工具这么写:

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

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

impl Screen {
    pub fn run(&self) {
        for component in self.components.iter() {
            component.draw();
        }
    }
}
  • 首先声明了一个公开的trait叫Draw,里面定义了一个方法draw,但没有写具体实现
  • 然后声明了一个公开的结构体叫Screen,它里面有一个公开的字段叫components。它的类型是Vector,里面的元素是Box<dyn Draw>
    Box<>用于定义trait对象,表示Box里的元素实现了Draw trait
  • 通过impl块为Screen写了run方法,一运行就把所有元素画出来

同样是表示某个类型实现某个/某些trait,为什么不适用泛型呢?来看看泛型的写法:

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

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

这是因为泛型Vec<T>只要T一固定下来这个Vector里就只能存储这个类型了。举个例子,假如第一个放进这个Vector的元素是Button类型,那么这个Vector的其他元素就只能是Button了(因为Vector里的所有元素类型必须相同)。

而如果是Vec<Box<dyn Draw>>,那么第一个放进去是Button类型,后面还可以放TextField类型,只要是实现了Draw trait的类型都可以放进去。

接下来我们来写实现了Draw trait的类型具体是什么样的:

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

impl Draw for Button {
    fn draw(&self) {
        // 绘制按钮
    }
}
  • 一个Button结构体可能有widthheightlabel字段,所以我们这么定义
  • 通过impl块为Button实现了Draw trait,里面的实际代码就忽略了

这只是lib.rs的内容,接下来到mian.rs写主程序:

use gui::Draw;

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

impl Draw for SelectBox {
    fn draw(&self) {
        // 绘制一个选择框
    }
}
  • main.rs里的结构体SelectBox有三个字段,具有 widthheightoptions字段
  • 通过impl块为SelectBox实现了Draw trait,里面的实际代码就忽略了

接着看主函数:

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();
}
  • 主程序里有一个Screen结构体的实例,里面放了SelectBox类型和Button类型(得使用Box::new()封装)。这个Vector能放不同类型的元素正是归功于定义trait对象。
  • 然后调用Screen上的方法run渲染出来即可。实际上run方法不管实际传进去是什么类型,只要这个类型实现了Draw trait即可。

17.2.3. trait对象执行的是动态派发

将trait bound作用于泛型时,Rust编译器会执行单态化:编译器会为我们用来替换泛型参数类型的每一个具体类型生成对应函数和方法的非泛型实现。

这点在 10.2. 泛型 中有阐述:


举个例子:

fn main() {
	let integer = Some(5);
	let float = Some(5.0)
}

这里integerOption<i32>floatOption<f64>,在编译的时候编译器会把Option<T>展开为Option_i32Option_f64

enum Option_i32 {
	Some(i32),
	None,
}

enum Option_f64 {
	Some(f64),
	None,
}

也就是把Option<T>这个泛型定义替换为了两个具体类型的定义。

单态后的main函数也变成了这样:

enum Option_i32 {
	Some(i32),
	None,
}

enum Option_f64 {
	Some(f64),
	None,
}

fn main(){
	let integer = Option_i32::Some(5);
	let float = Option_f64::Some(5.0);
}

通过单态化生成的代码会执行静态派发(static dispatch),在编译过程中确定调用的方法。

动态派发(dynamic dispatch) 无法爱编译过程中确定你调用的究竟是哪一种方法,编译器会产生额外的代码以便在运行时找出希望调用的方法。使用trait对象就会执行动态派发,代价是产生一些运行时的开销,并且阻止编译器内联方法代码,使得部分优化操作无法进行。

17.2.4. 使用trait对象必须保证对象安全

只能把满足对象安全(object-safe)的trait转化为trait对象。Rust使用了一系列规则来判定某个对象是否安全,只需要记住两条:

  • 方法的返回类型不是self
  • 方法不包含任何的泛型类型参数

看个例子:

pub trait Clone{
	fn clone(&self) -> self;
}

标准库里Clone trait和clone这个函数的签名如上所示,由于clone方法的返回值是self,所以Clone trait就不符合对象安全。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2286934.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

VLLM性能调优

1. 抢占 显存不够的时候&#xff0c;某些request会被抢占。其KV cache被清除&#xff0c;腾退给其他request&#xff0c;下次调度到它&#xff0c;重新计算KV cache。 报这条消息&#xff0c;说明已被抢占&#xff1a; WARNING 05-09 00:49:33 scheduler.py:1057 Sequence gr…

Java线程认识和Object的一些方法

本文目标&#xff1a; 要对Java线程有整体了解&#xff0c;深入认识到里面的一些方法和Object对象方法的区别。认识到Java对象的ObjectMonitor&#xff0c;这有助于后面的Synchronized和锁的认识。利用Synchronized wait/notify 完成一道经典的多线程题目&#xff1a;实现ABC…

数据库管理-第287期 Oracle DB 23.7新特性一览(20250124)

数据库管理287期 2025-01-24 数据库管理-第287期 Oracle DB 23.7新特性一览&#xff08;20250124&#xff09;1 AI向量搜索&#xff1a;算术和聚合运算2 更改Compatible至23.6.0&#xff0c;以使用23.6或更高版本中的新AI向量搜索功能3 Cloud Developer包4 DBMS_DEVELOPER.GET_…

【MySQL】MySQL客户端连接用 localhost和127.0.0.1的区别

# systemctl status mysqld # ss -tan | grep 3306 # mysql -V localhost与127.0.0.1的区别是什么&#xff1f; 相信有人会说是本地IP&#xff0c;曾有人说&#xff0c;用127.0.0.1比localhost好&#xff0c;可以减少一次解析。 看来这个入门问题还有人不清楚&#xff0c;其实…

MySQL(高级特性篇) 14 章——MySQL事务日志

事务有4种特性&#xff1a;原子性、一致性、隔离性和持久性 事务的隔离性由锁机制实现事务的原子性、一致性和持久性由事务的redo日志和undo日志来保证&#xff08;1&#xff09;REDO LOG称为重做日志&#xff0c;用来保证事务的持久性&#xff08;2&#xff09;UNDO LOG称为回…

【Block总结】HWD,小波下采样,适用分类、分割、目标检测等任务|即插即用

论文信息 Haar wavelet downsampling (HWD) 是一项针对语义分割的创新模块&#xff0c;旨在通过减少特征图的空间分辨率来提高深度卷积神经网络&#xff08;DCNNs&#xff09;的性能。该论文的主要贡献在于提出了一种新的下采样方法&#xff0c;能够在下采样阶段有效地减少信息…

【解决方案】MuMu模拟器移植系统进度条卡住98%无法打开

之前在Vmware虚拟机里配置了mumu模拟器&#xff0c;现在想要移植到宿主机中 1、虚拟机中的MuMu模拟器12-1是目标系统&#xff0c;对应的目录如下 C:\Program Files\Netease\MuMu Player 12\vms\MuMuPlayer-12.0-1 2、Vmware-虚拟机-设置-选项&#xff0c;启用共享文件夹 3、复…

力扣面试150 快乐数 循环链表找环 链表抽象 哈希

Problem: 202. 快乐数 &#x1f469;‍&#x1f3eb; 参考题解 Code public class Solution {public int squareSum(int n) {int sum 0;while(n > 0){int digit n % 10;sum digit * digit;n / 10;}return sum;}public boolean isHappy(int n) {int slow n, fast squa…

安卓(android)实现注册界面【Android移动开发基础案例教程(第2版)黑马程序员】

一、实验目的&#xff08;如果代码有错漏&#xff0c;可查看源码&#xff09; 1.掌握LinearLayout、RelativeLayout、FrameLayout等布局的综合使用。 2.掌握ImageView、TextView、EditText、CheckBox、Button、RadioGroup、RadioButton、ListView、RecyclerView等控件在项目中的…

SpringSecurity:There is no PasswordEncoder mapped for the id “null“

文章目录 一、情景说明二、分析三、解决 一、情景说明 在整合SpringSecurity功能的时候 我先是去实现认证功能 也就是&#xff0c;去数据库比对用户名和密码 相关的类&#xff1a; UserDetailsServiceImpl implements UserDetailsService 用于SpringSecurity查询数据库 Logi…

微服务入门(go)

微服务入门&#xff08;go&#xff09; 和单体服务对比&#xff1a;里面的服务仅仅用于某个特定的业务 一、领域驱动设计&#xff08;DDD&#xff09; 基本概念 领域和子域 领域&#xff1a;有范围的界限&#xff08;边界&#xff09; 子域&#xff1a;划分的小范围 核心域…

996引擎 - NPC-动态创建NPC

996引擎 - NPC-动态创建NPC 创建脚本服务端脚本客户端脚本添加自定义音效添加音效文件修改配置参考资料有个小问题,创建NPC时没有控制朝向的参数。所以。。。自己考虑怎么找补吧。 多重影分身 创建脚本 服务端脚本 Mir200\Envir\Market_Def\test\test001-3.lua -- NPC八门名…

基于MinIO的对象存储增删改查

MinIO是一个高性能的分布式对象存储服务。Python的minio库可操作MinIO&#xff0c;包括创建/列出存储桶、上传/下载/删除文件及列出文件。 查看帮助信息 minio.exe --help minio.exe server --help …

观察者模式和订阅发布模式的关系

有人把观察者模式等同于发布订阅模式&#xff0c;也有人认为这两种模式存在差异&#xff0c;本质上就是调度的方法不同。 发布订阅模式: 观察者模式: 相比较&#xff0c;发布订阅将发布者和观察者之间解耦。&#xff08;发布订阅有调度中心处理&#xff09;

(2025 年最新)MacOS Redis Desktop Manager中文版下载,附详细图文

MacOS Redis Desktop Manager中文版下载 大家好&#xff0c;今天给大家带来一款非常实用的 Redis 可视化工具——Redis Desktop Manager&#xff08;简称 RDM&#xff09;。相信很多开发者都用过 Redis 数据库&#xff0c;但如果你想要更高效、更方便地管理 Redis 数据&#x…

Baklib引领内容管理平台新时代优化创作流程与团队协作

内容概要 在迅速变化的数字化时代&#xff0c;内容管理平台已成为各种行业中不可或缺的工具。通过系统化的管理&#xff0c;用户能够有效地组织、存储和共享信息&#xff0c;从而提升工作效率和创意表达。Baklib作为一款新兴的内容管理平台&#xff0c;以其独特的优势和创新功…

Java实现.env文件读取敏感数据

文章目录 1.common-env-starter模块1.目录结构2.DotenvEnvironmentPostProcessor.java 在${xxx}解析之前执行&#xff0c;提前读取配置3.EnvProperties.java 这里的path只是为了代码提示4.EnvAutoConfiguration.java Env模块自动配置类5.spring.factories 自动配置和注册Enviro…

房屋租赁系统在数字化时代中如何重塑租赁服务与提升市场竞争力

内容概要 在当今快速发展的数字化时代&#xff0c;房屋租赁系统的作用愈发重要。随着市场需求的变化&#xff0c;租赁服务正面临着新的挑战与机遇。房屋租赁系统不仅仅是一个简单的管理工具&#xff0c;更是一个能够提升用户体验和市场竞争力的重要平台。其核心功能包括合同管…

C++ ——— 学习并使用 priority_queue 类

目录 何为 priority_queue 类 学习并使用 priority_queue 类 实例化一个 priority_queue 类对象 插入数据 遍历堆&#xff08;默认是大堆&#xff09; 通过改变实例化的模板参数修改为小堆 何为 priority_queue 类 priority_queue 类为 优先级队列&#xff0c;其本质就是…

DeepSeek 模型全览:探索不同类别的模型

DeepSeek 是近年来备受关注的 AI 研究团队&#xff0c;推出了一系列先进的深度学习模型&#xff0c;涵盖了大语言模型&#xff08;LLM&#xff09;、代码生成模型、多模态模型等多个领域。本文将大概介绍 DeepSeek 旗下的不同类别的模型&#xff0c;帮助你更好地理解它们的特点…