午夜视频在线网站,日韩视频精品在线,中文字幕精品一区二区三区在线,在线播放精品,1024你懂我懂的旧版人,欧美日韩一级黄色片,一区二区三区在线观看视频

分享

[Rust開發(fā)]用可視化案例講Rust編程6.動態(tài)分發(fā)與最終封裝

 godxiasad 2024-02-26 發(fā)布于北京

想到就發(fā)了,反正大家也不看

全系列合集
[Rust開發(fā)]用可視化案例講Rust編程1.用Rust畫個百度地圖

[Rust開發(fā)]用可視化案例講Rust編程2. 編碼的核心組成:函數(shù)

[Rust開發(fā)]用可視化案例講Rust編程3.函數(shù)分解與參數(shù)傳遞

[Rust開發(fā)]用可視化案例講Rust編程4.用泛型和特性實現(xiàn)自適配shapefile的讀取

[Rust開發(fā)]用可視化案例講Rust編程5.用泛型和特性實現(xiàn)自適配繪制和顏色設(shè)置


上一節(jié)本來準(zhǔn)備結(jié)束的,后來很多同學(xué)問,說我覺得處理顏色那個地方太麻煩了,憑什么要寫兩次?寫一次不行么?

這里涉及到了靜態(tài)語言的一個核心概念,即:函數(shù)單態(tài)化。

單態(tài)化(monomorphization),即 Rust 編譯器為每個調(diào)用生成一個單獨的、無運(yùn)行時開銷的函數(shù)副本,因此該函數(shù)副本的運(yùn)行效率與不使用泛型的函數(shù)的運(yùn)行效率是一致的。

這是Rust對于泛型這種高級語法的解決方案,Rust的編譯器,選擇了編譯期對此泛型的所有可能性,實現(xiàn)單態(tài)化,這樣可以選擇最高效率最低開銷的運(yùn)行。

所以,不管你寫不寫,最終編譯的時候,都會編譯成多個函數(shù),不過對于實現(xiàn)來說,靜態(tài)語言就只能靜態(tài)實現(xiàn),而對于提供對外調(diào)用接口的情況,自然是記憶開銷越小越好,正如我們前幾節(jié)寫的利用泛型返回讀取shapefile以及用泛型處理點線面的方法。

泛型這種東西,仁者見仁智者見智,有人說泛型實際上是加大了系統(tǒng)的復(fù)雜性和冗繁度,但是對于高層架構(gòu)人員來說,有泛型實在太方便了……所以就得到了一個比較主觀的說法:

—— 泛型就是給造輪子的人用的。

除了泛型,要實現(xiàn)這種方式,還可以用Rust的另外一個高級特性,動態(tài)反射,即在運(yùn)行時在檢測相關(guān)類型的信息:dyn。

dyn關(guān)鍵字用于強(qiáng)調(diào)相關(guān)trait的方法是動態(tài)分配的。要以這種方式使用trait,它必須是“對象安全”的。

與泛型參數(shù)或植入型特質(zhì)不同,編譯器不知道被傳遞的具體類型。也就是說,該類型已經(jīng)被抹去。因此,一個dyn Trait引用包含兩個指針。一個指針指向數(shù)據(jù)(例如,一個結(jié)構(gòu)的實例)。另一個指針指向方法調(diào)用名稱與函數(shù)指針的映射(被稱為虛擬方法表各vtable)。

impl trait 和 dyn trait 在Rust分別被稱為靜態(tài)分發(fā)和動態(tài)分發(fā),即當(dāng)代碼涉及多態(tài)時,需要某種機(jī)制決定實際調(diào)動類型。

看到這里,可能有同學(xué)就會覺得:

既然是高級特性,看不懂的同學(xué)就暫時別去糾結(jié)了,我們來看看下面這個簡單的例子:

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的方法,沒有靜態(tài)指定他的輸入?yún)?shù),而是用dyn這個關(guān)鍵字,這個就代表了Rust會采用動態(tài)分發(fā),即運(yùn)行的時候,才去確定它到底是什么內(nèi)型。

然后在方法里面,我們可以針對不同的參數(shù)類型要進(jìn)行匹配相應(yīng)的處理流程。這些參數(shù),可以是系統(tǒng)內(nèi)置的參數(shù),例如整型、浮點型,也可以是自定義的結(jié)構(gòu)。

例如我們定義的叫做year的結(jié)構(gòu)體,double的意思,就是明年,所以只需要加1就可以了。而定義的dog的參數(shù),默認(rèn)狗的最大年紀(jì)就是24歲,所以如果你輸入的狗的age小于12歲,則可以double,而大于12,直接清零……

測試如下:

可以看見最后兩個測試,如果輸入的狗子的年紀(jì)是8歲,double出來就是16,而輸入的是15,則直接清零了……

但是這種寫法,與傳統(tǒng)的impl for <類型> 實際上是一樣的,只是對外部而言,調(diào)用的只是一個方法而已。

不過這種寫法,很多人都覺得會破壞靜態(tài)語言的固定性,不建議這樣做,所以大家做個了解即可。

(從編譯器角度來說,函數(shù)單態(tài)化會把動態(tài)分發(fā)給編譯成N個單態(tài)化的函數(shù)……所以這樣寫,并不會減少最后release出來的結(jié)果)

我們也可以通過enum來實現(xiàn),參考上一節(jié)顏色那個部分即可。

用dyn的方式,你可以在參數(shù)里面?zhèn)魅肴我忸愋偷膮?shù),然后在運(yùn)行的時候在控制走哪條邏輯線,但是有沒有一種可能,可以控制輸入?yún)?shù)的類型,但是又可以根據(jù)類型進(jìn)行邏輯選擇的呢?答案當(dāng)然是有,那就是官方推薦的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進(jìn)行一個double處理。

之后針對i32、f32、String和dog四種類型,進(jìn)行了邏輯實現(xiàn),最后測試如下:

//先寫一個簡單的測試性功能調(diào)用文件

//因為我們在trait里面實現(xiàn)了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();
}

測試效果如下: 

如果在調(diào)用的時候,我們輸入了沒有定義的類型,IDE工具就會提示:

如果沒有IDE的話,編譯器就會自動檢測出來,說你輸入的參數(shù)類型是沒有被實現(xiàn)過的,不讓使用了:

而為什么可以這樣做,又涉及到Rust具備函數(shù)式編程的設(shè)計思想了……函數(shù)式編程里面,函數(shù)是一等公民,函數(shù)也是一種對象,是可以定義和傳遞的,所以這里也通常把這種trait叫做trait對象,如果要論起寫法來,下面兩種寫法效果是完全一樣的:

trait Trait {}

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

fn foo(arg: impl Trait) {
}

但是,在技術(shù)上,T: Trait 和 impl Trait 有著一個很重要的不同點。當(dāng)用前者時,可以使用turbo-fish語法在調(diào)用的時候指定T的類型,如 foo::(1)。在 impl Trait 的情況下,只要它在函數(shù)定義中使用了,不管什么地方,都不能再使用turbo-fish。


最后,我來封裝一下讀取shapefile的方法和構(gòu)造trace的方法,讓調(diào)用者不在關(guān)心具體的類型:

  • 直接讀取shape類型,并且轉(zhuǎn)換為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來構(gòu)造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
}
}

然后在調(diào)用的時候,就可以直接一擊完成了:

#[test]
fn draw_db_style2(){
let shp1 = "./data/shp/北京行政區(qū)劃.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);
}

繪制效果如下:

放大之后,效果如下:

注意:順義出現(xiàn)了一個白色底,是因為做數(shù)據(jù)的時候,順義因為首都機(jī)場出現(xiàn)了一個環(huán)形構(gòu)造,我們在繪制Polygon的時候,內(nèi)部環(huán)設(shè)置為了白色,如果不想用這個顏色,也可以直接設(shè)置為輸入色就可以了,如下所示:

打完收工。

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

https:///godxia/blog

008.用可視化案例講Rust編程

自取。

    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多