[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编程

自取。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

git可视化工具

Gitkraken GitKraken 是一款专门用于管理和协作Git仓库的图形化界面工具。它拥有友好直观的界面&#xff0c;使得Git的操作变得更加简单易用&#xff0c;尤其适合那些不熟悉Git命令行的开发者。GitKraken提供了丰富的功能&#xff0c;如代码审查、分支管理、仓库克隆、提交、推…

rabbitmq死信交换机,死信队列使用

背景 对于核心业务需要保证消息必须正常消费&#xff0c;就必须考虑消费失败的场景&#xff0c;rabbitmq提供了以下三种消费失败处理机制 直接reject&#xff0c;丢弃消息&#xff08;默认&#xff09;返回nack&#xff0c;消息重新入队列将失败消息投递到指定的交换机 对于核…

ubuntu-server部署hive-part4-部署hive

参照 https://blog.csdn.net/qq_41946216/article/details/134345137 操作系统版本&#xff1a;ubuntu-server-22.04.3 虚拟机&#xff1a;virtualbox7.0 部署hive 下载上传 下载地址 http://archive.apache.org/dist/hive/ apache-hive-3.1.3-bin.tar.gz 以root用户上传至…

财务数字化转型如何找到打通业财融合的关键点

敏捷应对外部复杂情况保持竞争力、加强内部协同联动实现降本增效、赋能流程再造推动企业在变化中进化……多重战略价值下&#xff0c;企业数字化变革已经从“可选项”变成了“必选项”。 财务数字化转型正在成为推动“高韧性”企业高增长的核心驱动力。现在&#xff0c;以财务助…

JavaScript基础代码练习之数列第n位

一、这段代码要求用户输入一个数字n&#xff0c;然后使用递归的方式计算斐波那契数列中第n位的值&#xff0c;并将结果以警告框的形式显示出来。斐波那契数列是一个经典的数学问题&#xff0c;其中每个数字是前两个数字的和&#xff0c;数列的前两个数字通常是1。因此&#xff…

【MATLAB源码-第28期】基于matlab的16QAM定时同步仿真,采用gardner算法,Costa锁相环。

操作环境&#xff1a; MATLAB 2022a 1、算法描述 正交幅度调制&#xff08;QAM&#xff0c;Quadrature Amplitude Modulation&#xff09;是一种在两个正交载波上进行幅度调制的调制方式。这两个载波通常是相位差为90度&#xff08;π/2&#xff09;的正弦波&#xff0c;因此…

DevOps与CI/CD简介

DevOps 是一种软件开发和运维的文化、实践和方法论&#xff0c;旨在通过加强开发团队和运维团队之间的合作和沟通&#xff0c;实现快速、高效、可靠的软件交付和运维。DevOps 是由 Development&#xff08;开发&#xff09;和 Operations&#xff08;运维&#xff09;两个单词组…

基于深度学习的常见车型识别系统(网页版+YOLOv8/v7/v6/v5代码+训练数据集)

摘要&#xff1a;在本博客中介绍了基于YOLOv8/v7/v6/v5的常见车型识别系统。核心技术采用YOLOv8&#xff0c;并融合了YOLOv7、YOLOv6、YOLOv5的算法优势&#xff0c;进行了细致的性能指标对比。详细介绍了国内外在常见车型识别方面的研究现状、数据集处理方法、算法原理、模型构…

Linux(centos) 安装GraalVM

文章目录 版权声明GraalVM 版权声明 本博客的内容基于我个人学习黑马程序员课程的学习笔记整理而成。我特此声明&#xff0c;所有版权属于黑马程序员或相关权利人所有。本博客的目的仅为个人学习和交流之用&#xff0c;并非商业用途。我在整理学习笔记的过程中尽力确保准确性&…

力扣-python-丑数

解答&#xff1a; class Solution:def isUgly(self, n: int) -> bool:if n < 0:return False# 将 n 依次除以 2、3、5&#xff0c;直到 n 不能再被这些因子整除while n % 2 0:n // 2while n % 3 0:n // 3while n % 5 0:n // 5return n 1class Solution:def isUgly(s…

手写简易操作系统(二十一)--硬盘驱动

前情提要 上面一节我们实现了 malloc 和 free 的系统调用&#xff0c;这一节我们来实现硬盘驱动。 一、硬盘分区 我们的文件系统安装在一块全新的硬盘中&#xff0c;我们先创建它&#xff0c;然后在给他分区。 1.1、创建硬盘 首先是创建&#xff0c;这个之前我们已经干过一…

《Git版本控制管理》笔记

第三章 git --version查看版本号git --help查看帮助文档裸双破折号分离参数 git diff -w master origin – tools/Makefile将当前目录或任何目录转化为Git版本库 git init 初始化之后项目目录中&#xff0c;有名为.git的文件git status 查看git状态git commit 提供日志消息和作…

Docker、Kubernetes之间的区别

比较容器化工具&#xff1a;了解 Docker、Kubernetes 在应用程序部署和管理方面的差异。 基本概述 Docker 是一个流行的容器化平台&#xff0c;允许开发人员在容器中创建、部署和运行应用程序。 Docker 提供了一组工具和 API&#xff0c;使开发人员能够构建和管理容器化应用程…

【星海随笔】Ubuntu22.04忘记密码

服务器篇&#xff1a; 有问题可留言。 第一步 远程console界面进入该设备 并重启该设备 如果看到这个界面情况 则点击右上角按钮 【发送 CtrlAltDelete】 调出grub启动菜单 NOTE&#xff1a;启动的后半段去点击这个按钮&#xff0c;前半段一直点会一直重启 如果是直连服务器&a…

AI智能客服机器人是什么?对企业重要吗?

在数字化时代&#xff0c;客户服务是企业与客户建立牢不可破关系的重要桥梁。AI智能客服机器人&#xff0c;顾名思义&#xff0c;就是利用人工智能技术提升客户服务体验的自动化工具。今天&#xff0c;就让我们来揭开AI智能客服机器人的神秘面纱&#xff0c;并讨论它对企业的重…

增强Java技能:使用OkHttp下载www.dianping.com信息

在这篇技术文章中&#xff0c;我们将探讨如何使用Java和OkHttp库来下载并解析www.dianping.com上的商家信息。我们的目标是获取商家名称、价格、评分和评论&#xff0c;并将这些数据存储到CSV文件中。此外&#xff0c;我们将使用爬虫代理来绕过任何潜在的IP限制&#xff0c;并实…

Ollama教程——入门:开启本地大型语言模型开发之旅

Ollama教程——入门&#xff1a;开启本地大型语言模型开发之旅 引言安装ollamamacOSWindows预览版LinuxDocker ollama的库和工具ollama-pythonollama-js 快速开始运行模型访问模型库 自定义模型从GGUF导入模型自定义提示 CLI参考创建模型拉取模型删除模型复制模型多行输入多模态…

越南工厂连接中国总部服务器解决方案---案例分享

随着全球化的不断深入&#xff0c;许多中国企业走出国门&#xff0c;在世界各地设立分支机构和生产基地。然而&#xff0c;随之而来的是跨国网络通信的挑战。近期&#xff0c;客户越南的工厂与中国总部之间的网络连接出现了一些问题&#xff0c;这直接影响了企业的日常运营效率…

Go-Gin中优雅的实现参数校验,自定义错误消息提示

问题描述 在参数校验的时候我们一般会基于"github.com/go-playground/validator/v10"这个库给结构体加标签实现校验参数&#xff0c;当参数校验错误的时候&#xff0c;他的提示一般是英文的&#xff0c;怎么自定义参数错误提示呢&#xff1f;跟着我一步步来 注册校…

备战蓝桥杯---贪心刷题1

话不多说&#xff0c;直接看题&#xff1a; 本质是一个数学题&#xff1a; 我们令xi<0表示反方向传递&#xff0c;易得我们就是求每一个xi的绝对值之和min,我们令平均值为a爸。 易得约束条件&#xff1a; x1-x2a1-a,x2-x3a2-a..... 解得x1x1-0,x2x1-((n-1)*a-a2-...an)。…