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、生命周期、包
并发线程间的数据共享
相关文章:
rust 实例化动态对象
在功能开发中,动态创建或获取某个对象的情况很多。在前端JS开发中,可以使用工厂函数,通过给定的类型标识创建不同的对象实例;还可以通过对象映射来实现动态创建对象。 在Rust中,我们也可以使用这两种方式去创建对象实…...
支持向量机 (Support Vector Machine, SVM)
支持向量机 (Support Vector Machine, SVM) 支持向量机(SVM)是一种广泛应用于分类、回归分析以及异常检测的监督学习算法。它基于结构风险最小化(Structural Risk Minimization,SRM)原则,通过寻找一个最优…...
C#初级教程(1)——C# 与.NET 框架:探索微软平台编程的强大组合
图片来源: https://www.lvhang.site/docs/dotnettimeline 即梦AI - 一站式AI创作平台 一、历史发展脉络 在早期的微软平台编程中,常用的编程语言有 Visual Basic、C、C。到了 20 世纪 90 年代末,Win32 API、MFC(Microsoft Found…...
Mac m1 连接公司内网
1、创建VPN 1、在系统偏好设置 2、选择网络 3、进行添加 2、添加设置 1、选择VPN 2、类型选择L2TP/IPSec 3、填写服务器IP和账号 4、点击认证设置-填写密码 。然后应用 3、进行特殊配置 网上说苹果系统的问题。 1、创建命令 sudo vim /etc/ppp/options 2、添加内容-主要别…...
C++:类与对象,定义类和构造函数
#define _CRT_SECURE_NO_WARNINGS 1 #include <iostream> using namespace std; //如何让定义一个类 // 封装 // 1、将数据和方法定义到一起。 // 2、把想给你看的数据给你看,不想给你看的封装起来。 通过访问限定符来实现 class Stack { public: //1.成…...
杨校老师课堂之信息学奥赛结构体操作使用经典题集锦汇总
C基础:结构体数组综合训练 员工信息处理系统题目描述输入描述输出描述解题思路参考代码 员工信息处理系统 题目描述 在一家企业中,员工信息的准确性和时效性是日常人事管理工作的关键。由于企业员工数量众多,手动统计与更新员工信息不仅耗费大量时间&a…...
8. Flink-CDC
1. Flink-CDC的介绍 Flink-cdc主要是用来同步数据库中的数据,它的主要优势在于基于Flink框架直接用Flink Stream Api 或Flink SQL 直接编程,不需要引入第三方组件 2.Flink-CDC的使用 Flink-cdc在使用上需要注意的点 注意Flink-cdc在2.1版本之前需要导…...
Windows 权限结构和原理:深入浅出
一、什么是权限? 权限,是指在操作系统或应用程序中,某个对象(如用户、程序、设备等)对特定资源的可操作范围。具体来说,权限控制了一个主体(通常是用户或应用程序)对某个资源&#…...
Nginx环境安装
一、官网地址 Nginx官网:http://nginx.org/ Nginx中文网:https://nginx.p2hp.com/ 二、Nginx版本 mainline version 开发版本stableversion 稳定版本legacy version 历史版本 三、Windows系统安装Nginx 第一步:选择Windows版本,…...
Spring AI + Ollama 实现调用DeepSeek-R1模型API
一、前言 随着人工智能技术的飞速发展,大语言模型(LLM)在各个领域的应用越来越广泛。DeepSeek 作为一款备受瞩目的国产大语言模型,凭借其强大的自然语言处理能力和丰富的知识储备,迅速成为业界关注的焦点。无论是文本生…...
android系统SystemServer进程启动流程分析
目录 一,SystemServer整体框架 二,SystemServer启动源码分析 2.1,重要的概念 2.2,启动入口 2.3,创建对应进程的binder 三,binder驱动和binder线程池 四,SystemServer真正启动方法 4.1 SystemServer main方法里面主要做了几件事情 1)创建SystemServiceManager管理所有的…...
【雅思博客06】Daily Life
对话 A: Honey, the house is such a mess! I need you to help me tidy up a bit. My boss and her husband are coming over for dinner, and the house needs to be spotless! B: I’m in the middle of something right now. I’ll be there in a second. A: This can’t …...
Oracle 深入理解Lock和Latch ,解析访问数据块全流程
Oracle 锁机制介绍 根据保护对象的不同,单实例Oracle数据库锁可以分为以下几大类: DML lock(data locks,数据锁):用于保护数据的完整性; DDL lock(dictionary locks,字典…...
19、《Springboot+MongoDB整合:玩转文档型数据库》
SpringbootMongoDB整合:玩转文档型数据库 摘要:本文全面讲解Spring Boot与MongoDB的整合实践,涵盖环境搭建、CRUD操作、聚合查询、事务管理、性能优化等核心内容。通过15个典型代码示例,演示如何高效操作文档数据库,深…...
如何基于transformers库通过训练Qwen/DeepSeek模型的传统分类能力实现文本分类任务
文章目录 模型与环境准备文档分析源码解读模型训练及推理方式进阶:CPU与显存的切换进阶:多卡数据并行训练🔑 DDP 训练过程核心步骤🚫 DDP 不适用于模型并行⚖️ DDP vs. Model Parallelism⚙️ 解决大模型训练的推荐方法🎉进入大模型应用与实战专栏 | 🚀查看更多专栏…...
Unity中一个节点实现植物动态(Shader)
1 . 核心思路就操作顶点作往复运动; 核心代码: half stage1 dot(positionOS, float3(0, 1, 0)) * _Strength; half stage2 sin(dot(positionOS, float3(1, 0, 0)) * _Strength _Time.y * _Speed); half stage3 stage1 * stage2 * float3(0.001,…...
PrimeTime:工具简介
相关阅读 PrimeTimehttps://blog.csdn.net/weixin_45791458/category_12900271.html?spm1001.2014.3001.5482 PrimeTime是PrimeTime Suite中的一个工具,能够执行全芯片级、门级的静态时序分析,这是芯片设计和分析流程中的一个关键部分。该工具通过检查…...
FFmpeg+WebSocket+JsMpeg实时视频流实现方案
之前写的使用FFmpeg Nginx HLS流媒体播放方案,适合对实时性要求不高的需求,存在延迟,FFmpeg需要将视频流存储到本地文件,而本次方案FFmpeg不需要将视频流存储到本地文件,而是直接将转换后的视频流(如MJPE…...
《论系统需求分析方法》写作心得 - 系统分析师
系统需求分析方法论述 一、项目概述及本人职责 本人曾参与一项企业级客户关系管理系统(CRM)的开发项目,担任系统分析师的角色。该项目旨在为企业提供一个集客户信息管理、销售过程跟踪、客户服务支持于一体的综合管理平台,以提升…...
DuodooBMS源码解读之 mrp_management模块
制造管理扩展模块用户使用手册 一、模块概述 本扩展模块是基于 Odoo 18 开发的制造管理模块,主要为用户提供了更为强大和细致的制造管理功能。该模块添加了数量验证功能,即当一步工序未完成时,开始下一步工序,则下一步工序的生产…...
rust笔记8-Deref与隐式解引用强制转换
Rust 的智能指针和 Deref Trait 是 Rust 中非常重要的概念,它们使得 Rust 的引用和指针操作更加灵活和安全。下面我们将深入介绍 Deref Trait、Deref 与 &、* 运算符的关系,以及 Rust 的隐式解引用强制转换(Deref Coercion)。 1. 智能指针与 Deref Trait 智能指针(如…...
【拜读】Tensor Product Attention Is All You Need姚期智团队开源兼容RoPE位置编码
姚期智团队开源新型注意力:张量积注意力(Tensor Product Attention,TPA)。有点像一种「动态的LoRA」,核心思路在于利用张量分解来压缩注意力机制中的 Q、K、V 表示,同时保留上下文信息,减少内存…...
Docker-技术架构演进之路
目录 一、概述 常见概念 二、架构演进 1.单机架构 2.应用数据分离架构 3.应用服务集群架构 4.读写分离 / 主从分离架构 5.引入缓存 —— 冷热分离架构 6.垂直分库 7.业务拆分 —— 微服务 8.容器化引入——容器编排架构 三、尾声 一、概述 在进行技术学习过程中&am…...
用Chrome Recorder轻松完成自动化测试脚本录制
前言 入门自动化测试,录制回放通常是小白测试首先用到的功能。而录制回放工具也一直是各大Web自动化测试必然会着重提供的一块功能。 早期WinRunner、QTP这样的工具,自动化测试可以说是围绕录制回放开展的。近年像Selenium也提供有录制工具 Selenium IDE,Playwright也包含…...
python中的异常-模块-包
文章目录 异常异常的定义异常捕获语法捕获常规异常捕获指定异常捕获多个异常捕获所有异常异常else异常finally 异常传递总结 模块概念导入自定义模块及导入main方法all变量 总结 包自定义包定义pycharm中建包的基本步骤导入方式 第三方包 异常 异常的定义 当检测到一个错误时…...
【GPU驱动】OpenGLES图形管线渲染机制
OpenGLES图形管线渲染机制 OpenGL/ES 的渲染管线也是一个典型的图形流水线(Graphics Pipeline),包括多个阶段,每个阶段都负责对图形数据进行处理。管线的核心目标是将图形数据转换为最终的图像,这些图像可以显示在屏幕…...
ssm-day06 ssm整合
从springMVC总结再回顾一下 60节 整合就是应用框架,并且把这个框架放到IOC容器中 web容器:装springMVC和controller相关的web组件 root容器:装业务和持久层相关的组件 子容器可以引用父容器中的组件,父容器不能调子容器 一个容器…...
AI 编程助手 cursor的系统提示词 prompt
# Role 你是一名极其优秀具有10年经验的产品经理和精通java编程语言的架构师。与你交流的用户是不懂代码的初中生,不善于表达产品和代码需求。你的工作对用户来说非常重要,完成后将获得10000美元奖励。 # Goal 你的目标是帮助用户以他容易理解的…...
Ubuntu 下 nginx-1.24.0 源码分析 - ngx_process_options
ngx_process_options 声明在 src\core\nginx.c static ngx_int_t ngx_process_options(ngx_cycle_t *cycle); 定义在 src\core\nginx.c static ngx_int_t ngx_process_options(ngx_cycle_t *cycle) {u_char *p;size_t len;if (ngx_prefix) {len ngx_strlen(ngx_prefix);p …...
ollama如何安全卸载,解决Ollama unins000.msg is missing
春节后在本地电脑安装了Ollama的客户端,每次开机自启,影响开机速度,而且本地的模型不如联网的回答效果好,果断选择了卸载,但是今天卸载发现提示下方的错误。根据此文章可以解决当前的问题。 根据此文章可以解决当前的…...
