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

news2024/11/24 6:59:59

全系列合集

[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.coloradmin.cn/o/1562011.html

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

相关文章

目标检测——服饰属性标签识别数据集

一、重要性及意义 首先&#xff0c;随着电商、时尚推荐等业务的发展&#xff0c;服饰属性标签识别已经成为一项关键的计算机视觉任务。这些标签&#xff0c;如颜色、款式、材质等&#xff0c;对于实现图像搜索、时尚推荐等业务需求至关重要。服饰属性标签识别数据集为此类任务…

用ChatGPT出题,完全做不完

最近小朋友正在学习加减法&#xff0c;正好利用ChatGPT来生成加减法练习题&#xff0c;小朋友表示够了&#xff0c;够了&#xff0c;完全做不完。本文将给大家介绍如何利用ChatGPT来生成练习题。 尚未获得ChatGPT的用户&#xff0c;请移步&#xff1a;五分钟开通GPT4.0。 角色…

谷粒商城实战(007 压力测试)

Java项目《谷粒商城》架构师级Java项目实战&#xff0c;对标阿里P6-P7&#xff0c;全网最强 总时长 104:45:00 共408P 此文章包含第141p-第p150的内容 简介 安装jmeter 安装jmeter 使用中文 这样写就是200个线程循环100次 一共是2万个请求 介绍线程组 添加请求 可以是htt…

设计模式之享元模式详解(下)

4&#xff09;完整解决方案-不带外部状态 1.结构图 IgoChessman充当抽象享元类&#xff0c;BlackIgoChessman和WhiteIgoChessman充当具体享元类&#xff0c;IgoChessmanFactory充当享元工厂类。 2.代码案例 抽象享元类 //围棋棋子类&#xff1a;抽象享元类 abstract class …

ES-7.12-官方文档阅读-ILM-Automate rollover

教程&#xff1a;使用ILM自动化滚动创建index 当你持续将带有时间戳的文档index到Elasticsearch当中时&#xff0c;通常会使用数据流&#xff08;data streams&#xff09;以便可以定义滚到到新索引。这是你能够实施一个hot-warm-cold架构来满足你的性能要强&#xff0c;控制随…

【单片机 5.3开关检测】

文章目录 前言一、5.3开关检测1.1没按键按下的1.2有按键按下的 二、改进1.改进 三、独立键盘3.1为什么要取反3.2 实用的按键 总结 前言 提示&#xff1a;这里可以添加本文要记录的大概内容&#xff1a; 课程需要&#xff1a; 提示&#xff1a;以下是本篇文章正文内容&#xf…

C++项目——集群聊天服务器项目(十一)服务器异常退出与添加好友业务

本节来实现C集群聊天服务器项目中的服务器异常退出与添加好友业务&#xff0c;一起来试试吧 一、服务器异常退出 在Linux环境下&#xff0c;我们在服务器端使用CTRLC结束程序执行&#xff0c;即使用CTRLC让服务器异常退出&#xff0c;这样的后果是本应登录服务器的用户在数据库…

2023年第十四届蓝桥杯 - 省赛 - Python研究生组 - A.工作时长

题目 数据文件&#xff1a;https://labfile.oss.aliyuncs.com/courses/21074/records.txt Idea 直接通过 datetime 模块加载时间字符串进行格式化&#xff0c;然后对时间列表进行排序&#xff0c;最后两两计算时间差。 Code Python from datetime import datetimetime_lis…

数论与线性代数——整除分块【数论分块】的【运用】【思考】【讲解】【证明(作者自己证的QWQ)】

文章目录 整除分块的思考与运用整除分块的时间复杂度证明 & 分块数量整除分块的公式 & 公式证明公式证明 代码code↓ 整除分块的思考与运用 整除分块是为了解决一个整数求和问题 题目的问题为&#xff1a; ∑ i 1 n ⌊ n i ⌋ \sum_{i1}^{n} \left \lfloor \frac{n}{…

ESP32 引脚分配

请注意&#xff0c;以下引脚分配参考适用于流行的 30 引脚ESP32 devkit v1开发板。 仅输入引脚 GPIO34~39是GPIs–仅输入的管脚。这些引脚没有内部上拉或下拉电阻。它们不能用作输出&#xff0c;因此只能将这些管脚用作输入&#xff1a;GPIO 34、GPIO 35、GPIO 36、GPIO 39 S…

【opencv】教程代码 —features2D(7)根据单应性矩阵估计相机坐标系下的物体位姿...

pose_from_homography.cpp从图像中找到棋盘角点并进行姿态估计 从图像中找到棋盘角点并显示 计算角点在世界坐标系中的位置 读取相机内参和畸变系数并校正图像中的角点 计算从3D点到2D点的单应性矩阵 通过奇异值分解(SVD)优化对旋转矩阵的估计 基于单应矩阵分解及其优化结果&am…

VSCode - 离线安装扩展python插件教程

1&#xff0c;下载插件 &#xff08;1&#xff09;首先使用浏览器打开 VSCode 插件市场link &#xff08;2&#xff09;进入插件主页&#xff0c;点击右侧的 Download Extension 链接&#xff0c;将离线安装包下载下来&#xff08;文件后缀为 .vsix&#xff09; 2&#xff0c;…

Django之REST framework环境搭建

一、环境搭建 Django REST framework是基于Django实现的一个RESTful风格API框架,能够帮助我们快速开发RESTful风格的API 官网:Home - Django REST framework 中文文档:主页 - Django REST framework中文站点 1.1、安装 Python3.8+ pip install django==4.1.1 pip inst…

RISC-V/ARM mcu OpenOCD 调试架构解析

Risc-v/ARM mcu OpenOCD 调试架构解析 最近有使用到risc-v的单片机&#xff0c;所以了解了下risc-v单片机的编译与调试环境的搭建&#xff0c;面试时问到risc-v的调试可参看以下内容。 risc-v根据官方的推荐&#xff0c;调试器服务是选择OpenOCD&#xff0c;DopenOCD(开放片上…

游戏引擎中的声音系统

一、声音基础 1.1 音量 声音振幅的大小 压强p&#xff1a;由声音引起的与环境大气压的局部偏差 1.2 音调 1.3 音色 1.4 降噪 1.5 人的听觉范围 1.6 电子音乐 将自然界中连续的音乐转换成离散的信号记录到内存中 采样 - 量化 - 编码 香农定理&#xff1a;采样频率是信…

ES6学习(五)

文章目录 Module 语法1.1 痛点介绍(1) 异步加载(2) 私密(3) 重名(4) 依赖 1.2 解决方法(1) 解决异步加载问题(2) 解决私密问题(3) 重名解决方法(4) 解决依赖问题 1.3 模块化使用案例 Module 语法 之前js 出现的某些痛点&#xff1a; 在script 中引入的变量名不可以重复&#…

04_Git开发流程

文章目录 Git开发创建阶段开发阶段合并阶段常用指令 Git开发 创建阶段 共建Git仓库&#xff0c;首次使用请使用git clone指令 git clone xxx.git在master/main主干上搭建起基本的项目结构和公共内容&#xff0c;将这些内容push到远程仓库 在Github上创建分支dev&#xff08;de…

2024最新GPT4.0使用教程:GPTs,AI绘画,AI换脸,AI绘画,文档分析一站式解决

一、前言 ChatGPT3.5、GPT4.0、相信对大家应该不感到陌生吧&#xff1f;简单来说&#xff0c;GPT-4技术比之前的GPT-3.5相对来说更加智能&#xff0c;会根据用户的要求生成多种内容甚至也可以和用户进行创作交流。 然而&#xff0c;GPT-4对普通用户来说都是需要额外付费才可以…

HarmonyOS 应用开发之TaskPool和Worker支持的序列化类型

TaskPool和Worker的底层模型为Actor模型&#xff0c;基于Actor模型的内存隔离特性&#xff0c;执行多线程任务和取得结果需要通过跨线程序列化传输。目前支持传输的数据对象可以分为 普通对象 、 可转移对象 、 可共享对象 、 Native绑定对象 四种。 普通对象 普通对象传输采…

《基础设施即代码(IaC)》译者序

随着信息技术的飞速发展&#xff0c;我们对基础设施的理解也在不断深化。传统的基础设施往往被看作是硬件和软件的堆砌&#xff0c;而现在&#xff0c;基础设施的概念已经发生了巨大的变化。在当今这个信息化、数字化的时代&#xff0c;基础设施已经成为了企业和组织运行的核心…