【Rust自学】17.2. 使用trait对象来存储不同值的类型
喜欢的话别忘了点赞、收藏加关注哦(加关注即可阅读全文),对接下来的教程有兴趣的可以关注专栏。谢谢喵!(=・ω・=)
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
方法进行绘制(例如:Button
、TextField
等元素)。
这样的需求在面向对象语言里(比如Java或C#)可以定义一个Component
父类,里面定义了draw
方法。接下来定义Button
、TextField
等类,继承于Component
这个父类。
上一篇文章中说了Rust并没有提供继承功能,所以想使用Rust来构建GUI工具就得使用其他方法——为共有行为定义一个trait
17.2.2. 为共有行为定义一个trait
首先澄清一些定义:在Rust里我们避免将struct
或enum
称为对象,因为它们与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>
whereT: 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
结构体可能有width
、height
和label
字段,所以我们这么定义 - 通过
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
有三个字段,具有width
、height
和options
字段- 通过
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)
}
这里integer
是Option<i32>
,float
是Option<f64>
,在编译的时候编译器会把Option<T>
展开为Option_i32
和Option_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就不符合对象安全。
相关文章:

【Rust自学】17.2. 使用trait对象来存储不同值的类型
喜欢的话别忘了点赞、收藏加关注哦(加关注即可阅读全文),对接下来的教程有兴趣的可以关注专栏。谢谢喵!(・ω・) 17.2.1. 需求 这篇文章以一个例子来介绍如何在Rust中使用trait对象来存储不同值的类型。 …...

初始化mysql报错cannot open shared object file: No such file or directory
报错展示 我在初始化msyql的时候报错:mysqld: error while loading shared libraries: libaio.so.1: cannot open shared object file: No such file or directory 解读: libaio包的作用是为了支持同步I/O。对于数据库之类的系统特别重要,因此…...

2025年1月22日(网络编程)
系统信息: ubuntu 16.04LTS Raspberry Pi Zero 2W 系统版本: 2024-10-22-raspios-bullseye-armhf Python 版本:Python 3.9.2 已安装 pip3 支持拍摄 1080p 30 (1092*1080), 720p 60 (1280*720), 60/90 (640*480) 已安装 vim 已安装 git 学习…...

Jason配置环境变量
jason官网 https://jason-lang.github.io/ https://github.com/jason-lang/jason/releases 步骤 安装 Java 21 或更高版本 安装 Visual Studio Code 根据操作系统,请按照以下具体步骤操作 视窗 下载 Jason 的最新版本,选择“jason-bin-3.3.0.zip”…...

蓝桥杯python语言基础(7)——自定义排序和二分查找
目录 一、自定义排序 (一)sorted (二)list.sort 二、二分查找 bisect 一、自定义排序 (一)sorted sorted() 函数会返回一个新的已排序列表,而列表的 sort() 方法会直接在原列表上进行排序…...

(开源)基于Django+Yolov8+Tensorflow的智能鸟类识别平台
1 项目简介(开源地址在文章结尾) 系统旨在为了帮助鸟类爱好者、学者、动物保护协会等群体更好的了解和保护鸟类动物。用户群体可以通过平台采集野外鸟类的保护动物照片和视频,甄别分类、实况分析鸟类保护动物,与全世界各地的用户&…...

后盾人JS--闭包明明白白
延伸函数环境生命周期 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</title> <…...

redis的分片集群模式
redis的分片集群模式 1 主从哨兵集群的问题和分片集群特点 主从哨兵集群可应对高并发写和高可用性,但是还有2个问题没有解决: (1)海量数据存储 (2)高并发写的问题 使用分片集群可解决,分片集群…...
Kiwi 安卓浏览器本月停止维护,扩展功能迁移至 Edge Canary
IT之家 1 月 25 日消息,科技媒体 Android Authority 今天(1 月 25 日)发布博文,报道称 Kiwi 安卓浏览器将于本月停止维护,相关扩展支持功能已整合到微软 Edge Canary 浏览器中。 开发者 Arnaud42 表示 Kiwi 安卓浏览器…...
我的AI工具箱Tauri+Django内容生产介绍和使用
在现代内容生产环境中,高效、自动化的工具能够显著提升生产力,降低人工成本。Tauri 与 Django 结合打造的工作箱,集成了强大的 音频处理、视频剪辑、内容下载 以及 AI 文章撰写 等模块,帮助用户在多媒体内容生产的各个环节实现高效…...

四.4 Redis 五大数据类型/结构的详细说明/详细使用( zset 有序集合数据类型详解和使用)
四.4 Redis 五大数据类型/结构的详细说明/详细使用( zset 有序集合数据类型详解和使用) 文章目录 四.4 Redis 五大数据类型/结构的详细说明/详细使用( zset 有序集合数据类型详解和使用)1. 有序集合 Zset(sorted set)2. zset 有序…...

Java---猜数字游戏
本篇文章所实现的是Java经典的猜数字游戏 , 运用简单代码来实现基本功能 目录 一.题目要求 二.游戏准备 三.代码实现 一.题目要求 随机生成一个1-100之间的整数(可以自己设置区间),提示用户猜测,猜大提示"猜大了",…...
网站快速收录:利用RSS订阅提升效率
本文转自:百万收录网 原文链接:https://www.baiwanshoulu.com/27.html 利用RSS订阅可以显著提升网站内容的更新和收录效率,以下是一些具体的方法和策略: 一、RSS订阅的基本原理 RSS(ReallySimpleSyndication或RichS…...
vue3第三部分--组件通信
title: 组件通信 date: 2025-01-28 12:00:00 tags:- 前端 categories:- 前端组件通信 目标:重点学习父子组件与兄弟组件的通信方式,以及插槽的作用与使用方式 父子组件通信 主要是通过props和自定义事件来实现 1.1 父 -> 子通信(通过 …...

DeepSeek R1-Zero vs. R1:强化学习推理的技术突破与应用前景
📌 引言:AI 推理的新时代 近年来,大语言模型(LLM) 的规模化扩展成为 AI 研究的主流方向。然而,LLM 的扩展是否真的能推动 通用人工智能(AGI) 的实现?DeepSeek 推出的 R1…...
matlab提取滚动轴承故障特征
为了精准、稳定地提取滚动轴承故障特征,提出了基于变分模态分解和奇异值分解的特征提取方法,采用标准模糊C均值聚类(fuzzy C means clustering, FCM)进行故障识 别。对同一负荷下的已知故障信号进行变分模态分解,利用 奇异值分解技术进一步提…...
数据结构与算法学习笔记----容斥原理
数据结构与算法学习笔记----容斥原理 author: 明月清了个风 first publish time: 2025.1.30 ps⭐️介绍了容斥原理的相关内容以及一道对应的应用例题。 Acwing 890. 能被整除的数 [原题链接](890. 能被整除的数 - AcWing题库) 给定一个整数 n n n和 m m m个不同的质数 p 1 …...
Java 知识速记:全面解析 final 关键字
Java 知识速记:全面解析 final 关键字 什么是 final 关键字? final 关键字是 Java 中的一个修饰符。它可以用于类、方法和变量,其作用是限制对这些元素的修改。究竟如何限制?我们来逐个分析。 final 在变量中的用法 1. 声明常…...

(笔记+作业)书生大模型实战营春节卷王班---L0G2000 Python 基础知识
学员闯关手册:https://aicarrier.feishu.cn/wiki/QtJnweAW1iFl8LkoMKGcsUS9nld 课程视频:https://www.bilibili.com/video/BV13U1VYmEUr/ 课程文档:https://github.com/InternLM/Tutorial/tree/camp4/docs/L0/Python 关卡作业:htt…...

9、Docker环境安装Nginx
一、拉取镜像 docker pull nginx:1.24.0二、创建映射目录 作用:是将docker中nginx的相关配置信息映射到外面,方便修改配置文件 1、创建目录 # cd home/ # mkdir nginx/ # cd nginx/ # mkdir conf html log2、生成容器 docker run -p 80:80 -d --name…...
web vue 项目 Docker化部署
Web 项目 Docker 化部署详细教程 目录 Web 项目 Docker 化部署概述Dockerfile 详解 构建阶段生产阶段 构建和运行 Docker 镜像 1. Web 项目 Docker 化部署概述 Docker 化部署的主要步骤分为以下几个阶段: 构建阶段(Build Stage):…...
椭圆曲线密码学(ECC)
一、ECC算法概述 椭圆曲线密码学(Elliptic Curve Cryptography)是基于椭圆曲线数学理论的公钥密码系统,由Neal Koblitz和Victor Miller在1985年独立提出。相比RSA,ECC在相同安全强度下密钥更短(256位ECC ≈ 3072位RSA…...

通过Wrangler CLI在worker中创建数据库和表
官方使用文档:Getting started Cloudflare D1 docs 创建数据库 在命令行中执行完成之后,会在本地和远程创建数据库: npx wranglerlatest d1 create prod-d1-tutorial 在cf中就可以看到数据库: 现在,您的Cloudfla…...

解决Ubuntu22.04 VMware失败的问题 ubuntu入门之二十八
现象1 打开VMware失败 Ubuntu升级之后打开VMware上报需要安装vmmon和vmnet,点击确认后如下提示 最终上报fail 解决方法 内核升级导致,需要在新内核下重新下载编译安装 查看版本 $ vmware -v VMware Workstation 17.5.1 build-23298084$ lsb_release…...

大数据零基础学习day1之环境准备和大数据初步理解
学习大数据会使用到多台Linux服务器。 一、环境准备 1、VMware 基于VMware构建Linux虚拟机 是大数据从业者或者IT从业者的必备技能之一也是成本低廉的方案 所以VMware虚拟机方案是必须要学习的。 (1)设置网关 打开VMware虚拟机,点击编辑…...
Rust 异步编程
Rust 异步编程 引言 Rust 是一种系统编程语言,以其高性能、安全性以及零成本抽象而著称。在多核处理器成为主流的今天,异步编程成为了一种提高应用性能、优化资源利用的有效手段。本文将深入探讨 Rust 异步编程的核心概念、常用库以及最佳实践。 异步编程基础 什么是异步…...

佰力博科技与您探讨热释电测量的几种方法
热释电的测量主要涉及热释电系数的测定,这是表征热释电材料性能的重要参数。热释电系数的测量方法主要包括静态法、动态法和积分电荷法。其中,积分电荷法最为常用,其原理是通过测量在电容器上积累的热释电电荷,从而确定热释电系数…...
Java求职者面试指南:Spring、Spring Boot、MyBatis框架与计算机基础问题解析
Java求职者面试指南:Spring、Spring Boot、MyBatis框架与计算机基础问题解析 一、第一轮提问(基础概念问题) 1. 请解释Spring框架的核心容器是什么?它在Spring中起到什么作用? Spring框架的核心容器是IoC容器&#…...

【JVM面试篇】高频八股汇总——类加载和类加载器
目录 1. 讲一下类加载过程? 2. Java创建对象的过程? 3. 对象的生命周期? 4. 类加载器有哪些? 5. 双亲委派模型的作用(好处)? 6. 讲一下类的加载和双亲委派原则? 7. 双亲委派模…...

永磁同步电机无速度算法--基于卡尔曼滤波器的滑模观测器
一、原理介绍 传统滑模观测器采用如下结构: 传统SMO中LPF会带来相位延迟和幅值衰减,并且需要额外的相位补偿。 采用扩展卡尔曼滤波器代替常用低通滤波器(LPF),可以去除高次谐波,并且不用相位补偿就可以获得一个误差较小的转子位…...