rust 实例化动态对象

在功能开发中,动态创建或获取某个对象的情况很多。在前端JS开发中,可以使用工厂函数,通过给定的类型标识创建不同的对象实例;还可以通过对象映射来实现动态创建对象。

Rust中,我们也可以使用这两种方式去创建对象实例,但实现书写的方式可能略有不同;rust还可以通过序列化JSON数据时进行枚举类型匹配。

我们定义好需要测试的数据结构体、方法。小狗、小猫有自己的字段、方法,它们有相同的字段name,也有相同的方法say

use serde_derive::{Deserialize, Serialize};#[derive(Deserialize, Serialize, Debug)]
struct Dog {name: String,work: String,
}
impl Dog {fn new(name: String, work: String) -> Dog {Dog { name, work }}fn say(&self) {println!("{} say wangwang", self.name);}
}#[derive(Deserialize, Serialize, Debug)]
struct Cat {name: String,age: i32,
}impl Cat {fn new(name: String) -> Cat {Cat { name: name, age: 0 }}fn say(&self) {println!("{} say miamiamia", self.name);}
}

序列化serde

我们在拿到JSON格式数据进行序列化时,在rust中是需要确定具体数据类型的,但是我们并不知道具体类型,因为现在有两种类型,要合为一种类型,就需要归集,使用枚举enum来定义可能的类型:

#[derive(Deserialize, Serialize, Debug)]
#[serde(untagged)]
enum Animal {Dog(Dog),Cat(Cat),
}

对于JSON格式和rust 结构体的互相转换,可以使用serde库。也正好利用JSON转结构体这一过程,利用转换机制来实现动态创建对象。

安装相关的库:

cargo add serde serde_derive serde_json

我们定义一个JSON格式数据,使用serde库进行反序列化,并使用match进行匹配:

fn main() {let data = r#"{"name":"admin","age":2}"#;let animal = serde_json::from_str(data).unwrap();match animal {Animal::Dog(dog) => {dog.say();}Animal::Cat(cat) => {cat.say();}};
}

测试运行,正常输出了cat say wangwang。我们修改JSON格式数据

let data = r#"
{"name":"admin","work":"play"
}
"#;

测试运行,正常输出了dog say wangwang,说明没有逻辑没有问题。

待优化的地方在于我们使用了match,如果我们需要在多个地方使用animal,那么这段匹配逻辑就无处不在了。当有很多方法时,无法控制具体调用哪个方法,就需要不停的去匹配。

我们可以将它们需要调用公共方法在枚举类型Animal定义一下,内部逻辑根据不同类型在调用各自的方法。

impl Animal {fn say(&self) {match self {Animal::Cat(cat) => cat.say(),Animal::Dog(dog) => dog.say(),}}
}

Animal定义公共方法say,然后在序列化JSON数据格式时,我们必须要指定数据类型:

fn main() {let data = r#"{"name":"admin","age":2,"work":"play"}"#;let animal: Animal = serde_json::from_str(data).unwrap();animal.say();
}

明确指定了animal: Animal,因为没有其他逻辑帮助rust推断出具体的类型是什么。也可以这么写let animal = serde_json::from_str::<Animal>(data).unwrap();

注意

需要注意的是:匹配的不同对象结构体的字段不能一致,否则会匹配到枚举的第一个;如果出现包含的情况,我们需要把被包含的结构体放在前面。

比如小猫也有work字段了:

#[derive(Deserialize, Serialize, Debug)]
struct Cat {name: String,age: i32,work: String,
}

这是我们再去匹配JSON格式数据,因为数据里有age,我们希望的是匹配小猫Cat,但是它里面完全包含了小狗的字段Dog,而且枚举Animal种小狗在前,所以会直接匹配小狗:

let data = r#"
{"name":"admin","age":2,"work":"play"
}
"#;

这样达不到我们想要的结果,所以需要注意调整枚举值的顺序,可以将复杂数据结构放到前面。将Cat放到前面就可以正常工作了。

#[derive(Deserialize, Serialize, Debug)]
#[serde(untagged)]
enum Animal {Cat(Cat)Dog(Dog),
}

动态类型匹配

上一个方式是我们拿到了具体对象的JSON数据,然后通过序列化,获取到对应的对象实例。如果我们只知道某个类型,需要根据类型初始化具体实例对象。

我们枚举实例对象的类型,定义字符串转枚举类型的方法:

#[derive(Deserialize, Serialize, Debug)]
#[serde(untagged)]
enum AnimalType {Dog,Cat
}
impl AnimalType {fn str_to_animal_type(str: &str) -> AnimalType {match str {"dog" => AnimalType::Dog,"cat" => AnimalType::Cat,_ => panic!("unknown type"),}}
}

调用AnimalType获取到枚举类型,然后通过匹配类型来实例化对象,这跟上面的序列化JSON格式后续处理方式一致。

fn main() {let names = "dog";match AnimalType::str_to_animal_type(names) {AnimalType::Dog => {let dog = Dog {name: "admin".to_string(),work: "play".to_string(),};dog.say();}AnimalType::Cat => {let cat = Cat {name: "admin".to_string(),age: 2,work: "play".to_string(),};cat.say();}}
}

Trait 特质

trait是rust中特有的类型,它可以定义对象的行为,然后可以被其他对象实现。实现它的对象可以拥有相同的行为,但是可以拥有不同的内部逻辑。

这可以保证我们在动态获取到不同的对象实例,调用它们的方法时保证方法存在。在创建动态对象时,因为不知掉具体大小,需要使用Box<dyn Trait>定义动态对象。

trait AnimalTrait {fn say(&self);
}

然后在各个类型中实现AnimalTrait,并实现公共方法say

impl AnimalTrait for Dog {fn say(&self) {println!("{} say wangwang", self.name);}
}impl AnimalTrait for Cat {fn say(&self) {println!("{} say miamiamia", self.name);}
}

定义类型都实现AnimalTrait的方法,就可以放心的使用Box<dyn AnimalTrait>提供的动态对象了。

impl AnimalType {fn str_to_animal(str: &str) -> Box<dyn AnimalTrait> {match str {"dog" => Box::new(Dog::new("admin".to_string(), "play".to_string())),"cat" => Box::new(Cat::new("test".to_string())),_ => panic!("unknown type"),}}
}

方法str_to_animal通过类型匹配获取到对应的实例对象,现在我们不需要再匹配里直接调用方法了。我们拿到动态对象,想调用那个方法就用哪个。

fn main() {let names = "dog";let animal = AnimalType::str_to_animal(names);animal.say();
}

这样就很方便的进行动态对象的传递,我们不需要关心该调用哪个方法,是否需要导入指定的方法。rust通过Box<dyn AnimalTrait>会自动调用合适的实现。

From/Into 类型强转

我们定义了AnimalTrait规范了动态对象的行为,它们在实现了AnimalTrait后,就可以根据动态对象调用它的公共方法了。

但在根据类型创建动态对象时,仍然定义了枚举AnimalType的方法str_to_animal并调用从而匹配到对应的动态对象。

我们还可以使用Fromtrait,通过让AnimalTrait实现Fromtrait,从而直接使用into方法让字符串类型转为动态对象。

impl From<&str> for Box<dyn AnimalTrait> {fn from(value: &str) -> Self {match value {"dog" => Box::new(Dog::new("admin".to_string(), "play".to_string())),"cat" => Box::new(Cat::new("test".to_string())),_ => panic!("unknown type"),}}
}

这样的实现可以减少在创建动态对象时的显示函数调用,我们在使用的时候直接调用into()方法即可:

fn main{let dog: Box<dyn AnimalTrait> = "dog".into();dog.say();
}

HashMap映射类型

以上实现方案难免都使用了match进行匹配,而我们在之前说的映射对象的实现,则可以避免match的匹配。

通过HashMap初始化类型映射结构体对象,在使用时通过自定义方法get传入指定的类型,得到动态类型。

struct AnimalFactory {map: HashMap<String, Box<dyn Fn() -> Box<dyn AnimalTrait>>>,
}

我们定义了一个结构体AnimalFactory,其中包含一个HashMap类型的字段map,用于存储类型与创建函数的映射关系。

注意到HashMap的值是一个闭包函数而不是直接动态类型,如果直接定义HashMap<String, Box<dyn AnimalTrait>>,我们在初始化时就必须实例化创建对象实例,这就导致具体对象的实例只有一个而避免不了处理所有权的问题。如果我们需要传递所有权,就必须使用Arc了。

定义了工厂结构体AnimalFactory,定义初始化函数new:

impl AnimalFactory {fn new() -> Self {map.insert("dog".to_string(),Box::new(|| {Box::new(Dog::new("admin".to_string(), "play".to_string())) as Box<dyn AnimalTrait>}) as Box<dyn Fn() -> Box<dyn AnimalTrait>>,);map.insert("cat".to_string(),Box::new(|| Box::new(Cat::new("test".to_string()))),);AnimalFactory { map }}
}

由于HashMap需要定义具体的类型,我们在插入类型Dog时无法匹配定义的Box<dyn Fn() -> Box<dyn AnimalTrait>>导致报错,这就需要我们手动强转类型。

为了简化类型书写,我们定义一个类型替代:

type AnimalDynType = Box<dyn Fn() -> Box<dyn AnimalTrait>>;

我们已经初始化了映射表,定义根据具体类型获取动态对象的方法:

impl AnimalFactory {fn get(&self, name: &str) -> Box<dyn AnimalTrait> {match self.map.get(name) {Some(create_fn) => create_fn(),None => panic!("not found"),}}
}

在使用时,首先创建一个AnimalFactory对象,然后调用get方法,传入具体的类型名称,即可获取对应的动态对象。

fn main() {let animal = AnimalFactory::new();let dog = animal.get_animal("dog");dog.say();
}

最后

这几种实现方式都有一定的使用场景,根据实际需求选择合适的方式。

往期关联文章:

rust 集合、错误处理、泛型、Trait、生命周期、包

并发线程间的数据共享

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

cs224w课程学习笔记-第2课

cs224w课程学习笔记-第2课 传统图学习 前言一、节点任务1、任务背景2、特征节点度3、特征节点中心性3.1 特征向量中心性&#xff08;Eigenvector Centrality&#xff09;3.2 中介中心性&#xff08;Betweenness Centrality&#xff09;3.3 接近中心性&#xff08;Closeness Cen…

Centos虚拟机扩展磁盘空间

Centos虚拟机扩展磁盘空间 扩展前后效果1 虚拟机vmware关机后&#xff0c;编辑2 扩展2.1 查看2.2 新建分区2.3 格式化新建分区ext42.3.1 格式化2.3.2 创建2.3.3 修改2.3.4 查看 2.4 扩容2.4.1 扩容2.4.1 查看 扩展前后效果 df -h1 虚拟机vmware关机后&#xff0c;编辑 2 扩展 …

1.13作业

1 if(!preg_match("/[0-9]|\~|\|\|\#|\\$|\%|\^|\&|\*|\&#xff08;|\&#xff09;|\-|\|\|\{|\[|\]|\}|\:|\|\"|\,|\<|\.|\>|\/|\?|\\\\/i", $c)){eval($c); 构造数组rce ?ceval(array_pop(next(get_defined_vars()))); post传参:asystem("c…

如何在 SpringBoot 项目使用 Redis 的 Pipeline 功能

本文是博主在批量存储聊天中用户状态和登陆信息到 Redis 缓存中时&#xff0c;使用到了 Pipeline 功能&#xff0c;并对此做出了整理。 一、Redis Pipeline 是什么 Redis 的 Pipeline 功能可以显著提升 Redis 操作的性能&#xff0c;性能提升的原因在于可以批量执行命令。当我…

力扣LeetCode: 2209 用地毯覆盖后的最少白色砖块

题目&#xff1a; 给你一个下标从 0 开始的 二进制 字符串 floor &#xff0c;它表示地板上砖块的颜色。 floor[i] 0 表示地板上第 i 块砖块的颜色是 黑色 。floor[i] 1 表示地板上第 i 块砖块的颜色是 白色 。 同时给你 numCarpets 和 carpetLen 。你有 numCarpets 条 黑…

RabbitMQ 消息队列

1. 消息队列是什么&#xff1f; 当用户注册成功后&#xff0c;就发送邮件。当邮件发送成功了&#xff0c;接口才会提示注册成功信息。但由于发送邮件&#xff0c;依赖于其他厂商的服务&#xff0c;有可能他们的接口会非常耗时。那么用户就一直要等着邮件发送成功了&#xff0c;…

【SQL实验】触发器

下载素材文件”tsgl”、“成绩管理”,将tsgl.bak和成绩管理.bak数据库还原到库中【导入操作在之前的文章中详细讲过】 触发器 1、为图书表设置更新触发器&#xff0c;根据总编号来更新书名、作者、出版社、分类号和单价(根据总编号找到相应记录&#xff0c;然后更新书名、作者…

Win10系统Docker+DeepSeek+ragflow搭建本地知识库

文章目录 1、安装ollama1.1 下载1.2 安装1.3 cmd命令行测试安装成功1.4 拉取模型2、安装ragflow2.1 下载项目2.2 通过docker拉取镜像安装2.3 查看docker日志是否安装成功3、模型配置3.1 第一次登录需要注册3.2 模型添加4、知识库配置4.1 创建知识库4.2 上传文档4.3 解析5、聊天…

redis的应用,缓存,分布式锁

1.应用 1.1可以用作缓存 作用&#xff1a;提交数据的查询效率&#xff0c;减少对数据库的访问频率 什么数据适合放入缓存 1.查询频率高&#xff0c;修改频率低 2.对安全系数比较低 如何实现 Service public class DeptServer {Autowiredprivate DeptMapper deptMapper;Auto…

springboot整合 xxl-job

文章目录 一、xxl-job是什么二、使用步骤 1. 下载并运行管理端代码2. 访问管理页面&#xff0c;确认是否启动成功3. 配置执行器【在自己的springboot项目中配置】4. 在页面上创建执行器和任务&#xff0c;与项目中绑定 总结参考 一、xxl-job是什么 XXL-JOB 是一个分布式任务调…

Jenkins 环境搭建---基于 Docker

前期准备 提前安装jdk、maven、nodeJs&#xff08;如果需要的话&#xff09; 创建 jenkins 环境目录&#xff0c;用来当做挂载卷 /data/jenkins/ 一&#xff1a;拉取 Jenkins 镜像 docker pull jenkins/jenkins:lts 二&#xff1a;设置 Jenkins挂载目录 mkdir -p ~/jen…

小米路由器 AX3000T 降级后无法正常使用,解决办法

问题描述 买了个 AX3000T 路由器&#xff0c;想安装 OpenWRT 或者 安装 Clash 使用&#xff0c;看教程说是需要降级到 v1.0.47 版本。 结果刷机之后路由器无法打开了&#xff0c;一直黄灯亮&#xff0c;中间灭一下&#xff0c;又是黄灯长亮&#xff0c;没有 WIFI 没有连接。以…

金融学-金融机构

前言 金融机构在金融体系运行体系运营中起着不可获缺的关键作用.如规则的制定与监管-中央银行,体系的运营证券公司,体系的供贷的参与者金融中介.本章将用一种说明我们的金融体系是怎样改进经济效率的经济分析,来讲述相关金融机构 金融结构的经济学分析 世界各国的金融体系在…

公网远程家里局域网电脑过程详细记录,包含设置路由器。

由于从校内迁居小区,校内需要远程控制访问小区内个人电脑,于是早些时间刚好自己是电信宽带,可以申请公网ipv4不需要花钱,所以就打电话直接申请即可,申请成功后访问光猫设备管理界面192.168.1.1,输入用户名密码登录超管(密码是网上查下就有了)设置了光猫为桥接模式,然后…

002 SpringCloudAlibaba整合 - Feign远程调用、Loadbalancer负载均衡

前文地址&#xff1a; 001 SpringCloudAlibaba整合 - Nacos注册配置中心、Sentinel流控、Zipkin链路追踪、Admin监控 文章目录 8.Feign远程调用、loadbalancer负载均衡整合1.OpenFeign整合1.引入依赖2.启动类添加EnableFeignClients注解3.yml配置4.日志配置5.远程调用测试6.服务…

基于javaweb的SpringBoot校园二手商品系统设计和实现(源码+文档+部署讲解)

技术范围&#xff1a;SpringBoot、Vue、SSM、HLMT、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、小程序、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容&#xff1a;免费功能设计、开题报告、任务书、中期检查PPT、系统功能实现、代码编写、论文编写和辅导、论…

国产开源PDF解析工具MinerU

前言 PDF的数据解析是一件较困难的事情&#xff0c;几乎所有商家都把PDF转WORD功能做成付费产品。 PDF是基于PostScript子集渲染的&#xff0c;PostScript是一门图灵完备的语言。而WORD需要的渲染&#xff0c;本质上是PDF能力的子集。大模型领域&#xff0c;我们的目标文件格…

stm32单片机个人学习笔记16(SPI通信协议)

前言 本篇文章属于stm32单片机&#xff08;以下简称单片机&#xff09;的学习笔记&#xff0c;来源于B站教学视频。下面是这位up主的视频链接。本文为个人学习笔记&#xff0c;只能做参考&#xff0c;细节方面建议观看视频&#xff0c;肯定受益匪浅。 STM32入门教程-2023版 细…

Springboot + Ollama + IDEA + DeepSeek 搭建本地deepseek简单调用示例

1. 版本说明 springboot 版本 3.3.8 Java 版本 17 spring-ai 版本 1.0.0-M5 deepseek 模型 deepseek-r1:7b 需要注意一下Ollama的使用版本&#xff1a; 2. springboot项目搭建 可以集成在自己的项目里&#xff0c;也可以到 spring.io 生成一个项目 生成的话&#xff0c;如下…

Ubuntu 的RabbitMQ安装

目录 1.安装Erlang 查看erlang版本 退出命令 2. 安装 RabbitMQ 3.确认安装结果 4.安装RabbitMQ管理界面 5.启动服务并访问 1.启动服务 2.查看服务状态 3.通过IP:port 访问界面 4.添加管理员用户 a&#xff09;添加用户名&#xff1a;admin&#xff0c;密码&#xff1…