[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;有不会的也可以当…

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

在面试的时候&#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;更新时采用…

类和对象(下)--- 初始化列表、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…

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…

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…

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

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

ssm018简易版营业厅宽带系统+jsp

营业厅宽带系统设计与实现 摘 要 现代经济快节奏发展以及不断完善升级的信息化技术&#xff0c;让传统数据信息的管理升级为软件存储&#xff0c;归纳&#xff0c;集中处理数据信息的管理方式。本营业厅宽带系统就是在这样的大环境下诞生&#xff0c;其可以帮助管理者在短时间…

QCustomPlot一、QCustomPlot基础及画图显示

1、QCustomPlot下载 QCustomPlot源码demo 根据需要选择需要的文件&#xff1a; 完整版。QCustomPlot.tar.gz 源代码例子帮助文档&#xff1b; 共享库。QCustomPlot-sharedlib.tar.gz 库编译和使用&#xff1b; 源代码。QCustomPlot-source.tar.gz 源代码 里面包含了很多QCusto…

夜晚兼职好选择:六大副业助你增收

晚上兼职&#xff0c;无疑是许多寻求额外收入人群的理想选择。以下为您精心推荐的六个副业&#xff0c;既适合晚间操作&#xff0c;又能让您在轻松愉悦中赚取额外收益。 网络调查与市场研究&#xff1a;利用晚上的闲暇时光&#xff0c;参与网络调查与市场研究&#xff0c;为企业…

TO-277肖特基二极管 散热效果好 型号齐全

市场对于肖特基二极管的需求非常旺盛&#xff0c;近日常有客户前来东沃电子咨询TO-277B 封装系列肖特基二极管选型、价格、交期、现货等方面的问题。东沃电子推出的TO-277B 封装系列肖特基产品&#xff0c;外形扁平&#xff0c;散热片外露设计&#xff0c;能够有效改善散热能力…

配置plsql链接Oracle数据库(新手)

配置plsql链接Oracle数据库 安装Oracle客户端 、安装plsql客户端并激活 配置tnsnames.ora文件&#xff08;路径D:\app\peter\Oracle\InstantClient\network\admin根据你的实际路径设置&#xff09; 配置文件如下 # tnsnames.ora Network Configuration File: D:\app\peter\O…

练习 16 Web [极客大挑战 2019]LoveSQL

extractvalue(1,concat(‘~’, (‘your sql’) ) )报错注入&#xff0c;注意爆破字段的时候表名有可能是table_name不是table_schema 有登录输入框 常规尝试一下 常规的万能密码&#xff0c;返回了一个“admin的密码”&#xff1a; Hello admin&#xff01; Your password is…

2024最新软件测试【测试理论+ app 测试】面试题(内附答案)

一、测试理论 3.1 你们原来项目的测试流程是怎么样的? 我们的测试流程主要有三个阶段&#xff1a;需求了解分析、测试准备、测试执行。 1、需求了解分析阶段 我们的 SE 会把需求文档给我们自己先去了解一到两天这样&#xff0c;之后我们会有一个需求澄清会议&#xff0c; …

JAVA基础02-Java语言基础以及编译准备工作

什么是JAVA语言 Java是一门面向对象的编程语言&#xff0c;不仅吸收了C语言的各种优点&#xff0c;还摒弃了C里难以理解的多继承、指针等概念&#xff0c;因此Java语言具有功能强大和简单易用的两个特征。 &#xff08;可以编写桌面应用程序、Web应用程序、分布式系统和嵌入式…