[Rust开发]用可视化案例讲Rust编程6.动态分发与最终封装

全系列合集

[Rust开发]用可视化案例讲Rust编程1.用Rust画个百度地图

[Rust开发]用可视化案例讲Rust编程2. 编码的核心组成:函数

[Rust开发]用可视化案例讲Rust编程3.函数分解与参数传递

[Rust开发]用可视化案例讲Rust编程4.用泛型和特性实现自适配shapefile的读取

[Rust开发]用可视化案例讲Rust编程5.用泛型和特性实现自适配绘制和颜色设置

上一节本来准备结束的,后来很多同学问,说我觉得处理颜色那个地方太麻烦了,凭什么要写两次?写一次不行么?

这里涉及到了静态语言的一个核心概念,即:函数单态化

单态化(monomorphization),即 Rust 编译器为每个调用生成一个单独的、无运行时开销的函数副本,因此该函数副本的运行效率与不使用泛型的函数的运行效率是一致的。

这是Rust对于泛型这种高级语法的解决方案,Rust的编译器,选择了编译期对此泛型的所有可能性,实现单态化,这样可以选择最高效率最低开销的运行。

所以,不管你写不写,最终编译的时候,都会编译成多个函数,不过对于实现来说,静态语言就只能静态实现,而对于提供对外调用接口的情况,自然是记忆开销越小越好,正如我们前几节写的利用泛型返回读取shapefile以及用泛型处理点线面的方法。

泛型这种东西,仁者见仁智者见智,有人说泛型实际上是加大了系统的复杂性和冗繁度,但是对于高层架构人员来说,有泛型实在太方便了……所以就得到了一个比较主观的说法:

—— 泛型就是给造轮子的人用的。

除了泛型,要实现这种方式,还可以用Rust的另外一个高级特性,动态反射,即在运行时在检测相关类型的信息:dyn。

dyn关键字用于强调相关trait的方法是动态分配的。要以这种方式使用trait,它必须是“对象安全”的。

与泛型参数或植入型特质不同,编译器不知道被传递的具体类型。也就是说,该类型已经被抹去。因此,一个dyn Trait引用包含两个指针。一个指针指向数据(例如,一个结构的实例)。另一个指针指向方法调用名称与函数指针的映射(被称为虚拟方法表各vtable)。

impl trait 和 dyn trait 在Rust分别被称为静态分发和动态分发,即当代码涉及多态时,需要某种机制决定实际调动类型。

看到这里,可能有同学就会觉得:

图片

既然是高级特性,看不懂的同学就暂时别去纠结了,我们来看看下面这个简单的例子:

use std::{any::Any, ops::Add};
#[derive(Debug)]
struct year{y:usize
}
#[derive(Debug,Clone)]
struct dog{name:String,age:usize,
}fn double(s: &dyn Any){if let Some(v) = s.downcast_ref::<u32>() {println!("u32 double= {:?}",*v * 2);}else if let Some(v) = s.downcast_ref::<f32>() {println!("f32 double= {:?}",*v * 2.0);}else if let Some(v) = s.downcast_ref::<String>() {let x = v.clone();let x2 = v.clone();println!("string double= {:?}",x.add("_").add(&x2));}else if let Some(v) = s.downcast_ref::<year>() {let y = year{y:v.y +1};println!("year double= {:?}",y);}else if let Some(v) = s.downcast_ref::<dog>() {let mut d = dog{name:v.name.clone(), age:v.age};if d.age > 12{d.age =0;}else{d.age =d.age * 2;}println!("dog double= {:?}",d);}
}

这里定义了一个叫做double的方法,没有静态指定他的输入参数,而是用dyn这个关键字,这个就代表了Rust会采用动态分发,即运行的时候,才去确定它到底是什么内型。

然后在方法里面,我们可以针对不同的参数类型要进行匹配相应的处理流程。这些参数,可以是系统内置的参数,例如整型、浮点型,也可以是自定义的结构。

例如我们定义的叫做year的结构体,double的意思,就是明年,所以只需要加1就可以了。而定义的dog的参数,默认狗的最大年纪就是24岁,所以如果你输入的狗的age小于12岁,则可以double,而大于12,直接清零……

测试如下:

图片

可以看见最后两个测试,如果输入的狗子的年纪是8岁,double出来就是16,而输入的是15,则直接清零了……

但是这种写法,与传统的impl for <类型> 实际上是一样的,只是对外部而言,调用的只是一个方法而已。

不过这种写法,很多人都觉得会破坏静态语言的固定性,不建议这样做,所以大家做个了解即可。

(从编译器角度来说,函数单态化会把动态分发给编译成N个单态化的函数……所以这样写,并不会减少最后release出来的结果)

我们也可以通过enum来实现,参考上一节颜色那个部分即可。

用dyn的方式,你可以在参数里面传入任意类型的参数,然后在运行的时候在控制走哪条逻辑线,但是有没有一种可能,可以控制输入参数的类型,但是又可以根据类型进行逻辑选择的呢?答案当然是有,那就是官方推荐的impl trait 模式。

而且官方在1.26之后的版本里面,推荐使用impl trait的方式来编写类型可控的泛型,如下所示:

trait my_type:std::fmt::Debug+'static+Any{fn double(&self);
}impl my_type for i32{fn double(&self) {println!("i32 double= {:?}",self * self);}
}
impl my_type for f32{fn double(&self) {println!("f32 double= {:?}",self * self);}
}
impl my_type for String{fn double(&self) {println!("String double= {}_{}",self,self);}
}
impl my_type for dog{fn double(&self) {let mut d2 = self.clone();d2.age = d2.age +1;println!("dog double= {:?}",d2);}
}

代码非常简单,定义了一个trait,然后里面有一个方法,就是针对这个trait进行一个double处理。

之后针对i32、f32、String和dog四种类型,进行了逻辑实现,最后测试如下:


//先写一个简单的测试性功能调用文件//因为我们在trait里面实现了Any类型,所以有type_id这个方法能够获取对象类型唯一值fn show_my_type(s: impl my_type){
if s.type_id() ==TypeId::of::<i32>(){
println!("i32 = {:?}",s);
}
else if s.type_id() ==TypeId::of::<f32>(){
println!("f32 = {:?}",s);}
else if s.type_id() ==TypeId::of::<String>(){
println!("String = {:?}",s);
}
else if s.type_id() ==TypeId::of::<dog>(){
println!("dog = {:?}",s);
}
s.double();
}

测试效果如下: 

图片

如果在调用的时候,我们输入了没有定义的类型,IDE工具就会提示:

图片

如果没有IDE的话,编译器就会自动检测出来,说你输入的参数类型是没有被实现过的,不让使用了:

图片

而为什么可以这样做,又涉及到Rust具备函数式编程的设计思想了……函数式编程里面,函数是一等公民,函数也是一种对象,是可以定义和传递的,所以这里也通常把这种trait叫做trait对象,如果要论起写法来,下面两种写法效果是完全一样的:

trait Trait {}fn foo<T: Trait>(arg: T) {
}fn foo(arg: impl Trait) {
}

但是,在技术上,T: Trait 和 impl Trait 有着一个很重要的不同点。当用前者时,可以使用turbo-fish语法在调用的时候指定T的类型,如 foo::(1)。在 impl Trait 的情况下,只要它在函数定义中使用了,不管什么地方,都不能再使用turbo-fish。


最后,我来封装一下读取shapefile的方法和构造trace的方法,让调用者不在关心具体的类型:

  • 直接读取shape类型,并且转换为Geometry

pub fn shapeToGeometry(shp_path:&str)-> Vec<Geometry>{let shps:Vec<Shape> = shapefile::read_shapes(shp_path).expect(&format!("Could not open shapefile, error: {}", shp_path));let mut geometrys:Vec<Geometry> = Vec::new();for s in shps{geometrys.push(Geometry::<f64>::try_from(s).unwrap())}geometrys
}
用Geometry来构造trace:impl BuildTrace for traceParam<Geometry>{fn build_trace(&self) -> Vec<Box<ScatterMapbox<f64,f64>>> {let mut traces: Vec<Box<ScatterMapbox<f64,f64>>> = Vec::new();for (geom,color) in zip(self.geometrys.iter(),self.colors.iter()){let mut tr = match geom {Geometry::Point(_)=>{let p:Point<_> = geom.to_owned().try_into().unwrap();traceParam{geometrys:vec![p],colors:vec![color.to_owned()],size:self.size}.build_trace()},Geometry::MultiPoint(_)=>{let p:MultiPoint<_> = geom.to_owned().try_into().unwrap();let pnts:Vec<Point> = p.iter().map(|p|p.to_owned()).collect();let color = (0..pnts.len()).map(|i|color.to_owned()).collect();traceParam{geometrys:pnts,colors:color,size:self.size}.build_trace()},Geometry::LineString(_)=>{let p:LineString<_> = geom.to_owned().try_into().unwrap();traceParam{geometrys:vec![p],colors:vec![color.to_owned()],size:self.size}.build_trace()},Geometry::MultiLineString(_)=>{let p:MultiLineString<_> = geom.to_owned().try_into().unwrap();let lines:Vec<LineString> = p.iter().map(|p|p.to_owned()).collect();let color = (0..lines.len()).map(|i|color.to_owned()).collect();traceParam{geometrys:lines,colors:color,size:self.size}.build_trace()},Geometry::Polygon(_)=>{let p:Polygon<_> = geom.to_owned().try_into().unwrap();traceParam{geometrys:vec![p],colors:vec![color.to_owned()],size:self.size}.build_trace()},Geometry::MultiPolygon(_)=>{let p:MultiPolygon<_> = geom.to_owned().try_into().unwrap();let poly:Vec<Polygon> = p.iter().map(|p|p.to_owned()).collect();let color = (0..poly.len()).map(|i|color.to_owned()).collect();traceParam{geometrys:poly,colors:color,size:self.size}.build_trace()},_ => panic!("no geometry"),};traces.append(&mut tr);}traces}
}
然后在调用的时候,就可以直接一击完成了:#[test]
fn draw_db_style2(){let shp1 = "./data/shp/北京行政区划.shp";let color1 = inputColor::Rgba(Rgba::new(240,243,250,1.0));let shp2 = "./data/shp/面状水系.shp";let color2 = inputColor::Rgba(Rgba::new(108,213,250,1.0));let shp3 = "./data/shp/植被.shp";let color3 = inputColor::Rgba(Rgba::new(172,232,207,1.0));let shp4 = "./data/shp/高速.shp";let color4 = inputColor::Rgba(Rgba::new(255,182,118,1.0));let shp5 = "./data/shp/快速路.shp";let color5 = inputColor::Rgba(Rgba::new(255,216,107,1.0));let mut traces:Vec<Box<ScatterMapbox<f64,f64>>>= Vec::new();for (shp_path,color) in zip(vec![shp1,shp2,shp3,shp4,shp5],vec![color1,color2,color3,color4,color5]) {let gs = readShapefile::shapeToGeometry(shp_path);let colors:Vec<inputColor> = (0..gs.len()).map(|x|color.to_owned()).collect();let mut t = traceParam{geometrys:gs,colors:colors,size:2}.build_trace();traces.append(&mut t);}plot_draw_trace(traces,None);
}

绘制效果如下:

图片

放大之后,效果如下:

图片

注意:顺义出现了一个白色底,是因为做数据的时候,顺义因为首都机场出现了一个环形构造,我们在绘制Polygon的时候,内部环设置为了白色,如果不想用这个颜色,也可以直接设置为输入色就可以了,如下所示:

图片

图片

打完收工。

所有例子和代码在以下位置:

https://gitee.com/godxia/blog

008.用可视化案例讲Rust编程

自取。

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

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

相关文章

YOLOv8全网独家改进: 小目标 |新颖的多尺度前馈网络(MSFN) | 2024年4月最新成果

💡💡💡本文独家改进:多尺度前馈网络(MSFN),通过提取不同尺度的特征来增强特征提取能力,2024年最新的改进思路 💡💡💡创新点:多尺度前馈网络创新十足,抢先使用 💡💡💡如何跟YOLOv8结合:1)放在backbone后增强对全局和局部特征的提取能力;2)放在detect…

C语言一维数组及二维数组详解

引言&#xff1a; 小伙伴们&#xff0c;我发现我正文更新的有些慢&#xff0c;但相信我&#xff0c;每一篇文章真的都很用心在写的&#xff0c;哈哈&#xff0c;在本篇博客当中我们将详细讲解一下C语言中的数组知识&#xff0c;方便大家后续的使用&#xff0c;有不会的也可以当…

每日一题:C语言经典例题之水仙花

题目描述 春天是鲜花的季节&#xff0c;水仙花就是其中最迷人的代表&#xff0c;数学上有个水仙花数&#xff0c;它是这样定义的&#xff1a;“水仙花数”是指一个三位数&#xff0c;它的各位数字的立方和等于其本身&#xff0c;比如&#xff1a;153135333。请输出所有的“水仙…

公司只有一个测试,要怎么继续呆下去?

在面试的时候&#xff0c;面试官可能会问&#xff1a;小公司、小团队&#xff0c;岗位就你一个人&#xff0c;怎么做 &#xff1f; 或者已经有的小伙伴已经在公司中面临只有一个测试的处境&#xff0c;这个时候我们应该怎么处理呢&#xff1f; 一 原因分析 公司只有一个测试人…

OSPF中配置静态路由实验简述

静态路由协议和OSPF&#xff08;开放最短路径优先&#xff09;协议是两种常见的路由协议&#xff0c;它们在路由选择和网络管理方面有一些区别。他们可以共存。 静态路由协议需要手动配置路由表&#xff0c;不会自动适应网络拓扑变化&#xff0c;适用于小型网络或者网络拓扑变化…

MySQL Innodb 引擎中预防 Update 操作上升为表锁

一、MySQL 如何预防 Update 上升为表锁 在 MySQL 中&#xff0c;进行任何数据的 修改 操作都会进行一定的锁操作&#xff0c;而锁的不同直接导致性能的差异。例如 MyISAM 引擎&#xff0c;更新时采用表锁&#xff0c;并发性较差。而 Innodb 引擎支持事务&#xff0c;更新时采用…

Windows SDK(五)按钮静态文本与编辑框控件

我们首先应该知道&#xff0c;所谓按钮静态文本等等控件都是窗口&#xff0c;他们都是隶属于父窗口下的子窗口&#xff0c;所 以在创建控件前&#xff0c;我们要首先创建一个父窗口&#xff0c;此处我们直接使用Windows桌面程序创建时&#xff0c;程 序自动为我们创建的一个窗…

类和对象(下)--- 初始化列表、explicit、友元、static、匿名对象和内部类

本篇将会对类和对象的主要知识收尾&#xff0c;先会对构造函数进行补充&#xff0c;分别补充了构造函数体赋值、初始化列表、explicit 关键字&#xff0c;然后介绍 static 成员知识以及友元、内部类还有匿名对象等知识点&#xff0c;目录如下&#xff1a; 目录 1. 构造函数补充…

VUE3——生命周期

Vue3.0中可以继续使用Vue2.x中的生命周期钩子&#xff0c;但有有两个被更名&#xff1a; beforeDestroy改名为 beforeUnmountdestroyed改名为 unmounted Vue3.0也提供了 Composition API 形式的生命周期钩子&#xff0c;与Vue2.x中钩子对应关系如下&#xff1a; beforeCreate&g…

【ROS 笔记5】话题发布与订阅————高级(一)

1. 前言 在使用ros pulisher时, 我们在建立话题 pub = rospy.Publisher(chatter, String, queue_size=10) 我们的目的时将我们的message (string)通过话题发布出去,如:pub.publish(hello_str)。 如果是为了处理单个话题的问题, 我们只用一个pub.publish()去发布就好。 …

matlab学习(一)(3.26-4.1)

一.信息隐藏的概念 信息隐藏是利用一些技术&#xff0c;使得要隐藏的信息是用眼睛看不出变化的。它将秘密信息隐藏在可公开的媒体信息中&#xff0c;使人们凭直观的视觉和听觉难以察觉基存在。 二.空域信息隐藏技术 空域信息隐藏是将信息直接嵌入到图像的像素值中的技术。主要…

洛谷 P2658 汽车拉力比赛

思路&#xff1a;二分BFS 题目的大意就是找出一个难度系数&#xff0c;让到达每一个路标之间的相邻格子的高度之差为难度系数。 所以&#xff0c;想要找到这个难度系数&#xff0c;我们需要不断地枚举数据范围内的数据&#xff0c;然后一个一个试试&#xff0c;全部BFS遍历一…

Mysql 和 Redis 数据怎么保持一致性,怎么实现

MySQL 和 Redis 是两种不同类型的数据库&#xff0c;MySQL 是关系型数据库&#xff0c;而 Redis 是键值存储的非关系型数据库。它们在数据一致性的保证上需要采取不同的策略&#xff0c;尤其是在分布式环境中。以下是一些保持 MySQL 和 Redis 数据一致性的常见策略&#xff1a;…

ISELED-演示项目代码

目录 一、main函数二、点灯函数一、main函数 int main(void) {/* Write your local variable definition here */iseledInitType.crcEnable = 1;iseledInitType.firstLedAdr = 1;iseledInitType.tempCmpEnable = 0;iseledInitType.voltSwing = 0;/*** End of Processor Expert…

简单了解裸眼3D呈现技术

裸眼3D呈现是一种不需要佩戴任何特殊设备&#xff08;如3D眼镜或头盔&#xff09;即可观看到3D效果的技术。这种技术近年来得到了快速发展&#xff0c;为观众带来了更加沉浸式的视觉体验。 实现裸眼3D呈现的关键步骤包括&#xff1a; 创建立体图像源&#xff1a;首先需要有一…

HWOD:记录正负数

一、知识点 1、scanf()的返回值 scanf()返回值类型为int&#xff0c;返回转换成功的个数 有代码int temp; scanf("%d",&temp); 在屏幕输入一个数字&#xff0c;比如5&#xff0c;回车&#xff0c;scanf()返回1 在屏幕输入一个字符或字符串&#xff0c;比…

STM32 M3内核寄存器概念

内容主要来自<<M3内核权威指南>> 汇编程序中的最低有效位&#xff08;Least Significant Bit&#xff09;。LSB是二进制数中最右边的位&#xff0c;它代表了数值中的最小单位。在汇编程序中&#xff0c;LSB通常用于表示数据的最小精度或者作为标志位。 ---------…

人工智能|深度学习——基于Xception算法模型实现一个图像分类识别系统

一、Xception简介 在计算机视觉领域&#xff0c;图像识别是一个非常重要的任务&#xff0c;其应用涵盖了人脸识别、物体检测、场景理解等众多领域。随着深度学习技术的发展&#xff0c;深度卷积神经网络&#xff08;Convolutional Neural Networks&#xff0c;简称CNN&#xff…

linux下tar命令的压缩和解压详细使用方法

在Linux系统中&#xff0c;tar命令用于创建、查看、提取和解压 tar 存档文件。以下是 tar 命令的一些常见用法&#xff1a; 压缩文件或目录&#xff1a; tar -czvf archive.tar.gz /path/to/directory # 压缩目录为 .tar.gz 格式的文件tar -czvf archive.tar.gz file1 file2 …

测试人员前期参与设计方案时需要注意什么?

服务的健壮性跟系统设计有很大关系&#xff0c;前期设计时考虑多一些处理逻辑&#xff0c;可以避免后期出现问题带来的损失以及修复问题的成本。 在前期讨论设计方案时测试同学也需要参与&#xff0c;而不只是埋头设计用例和测试&#xff0c;开发同学可能因为思维局限或者思考…