Rust中實(shí)例化動(dòng)態(tài)對(duì)象的示例詳解
在功能開(kāi)發(fā)中,動(dòng)態(tài)創(chuàng)建或獲取某個(gè)對(duì)象的情況很多。在前端JS開(kāi)發(fā)中,可以使用工廠函數(shù),通過(guò)給定的類型標(biāo)識(shí)創(chuàng)建不同的對(duì)象實(shí)例;還可以通過(guò)對(duì)象映射來(lái)實(shí)現(xiàn)動(dòng)態(tài)創(chuàng)建對(duì)象。
在Rust
中,我們也可以使用這兩種方式去創(chuàng)建對(duì)象實(shí)例,但實(shí)現(xiàn)書(shū)寫(xiě)的方式可能略有不同;rust
還可以通過(guò)序列化JSON
數(shù)據(jù)時(shí)進(jìn)行枚舉類型匹配。
我們定義好需要測(cè)試的數(shù)據(jù)結(jié)構(gòu)體、方法。小狗、小貓有自己的字段、方法,它們有相同的字段name
,也有相同的方法say
。
use serde_derive::{Deserialize, Serialize}; #[derive(Deserialize, Serialize, Debug)] struct Dog { name: String, work: String, } impl Dog { fn new(name: String, work: String) -> Dog { Dog { name, work } } fn say(&self) { println!("{} say wangwang", self.name); } } #[derive(Deserialize, Serialize, Debug)] struct Cat { name: String, age: i32, } impl Cat { fn new(name: String) -> Cat { Cat { name: name, age: 0 } } fn say(&self) { println!("{} say miamiamia", self.name); } }
序列化serde
我們?cè)谀玫?code>JSON格式數(shù)據(jù)進(jìn)行序列化時(shí),在rust
中是需要確定具體數(shù)據(jù)類型的,但是我們并不知道具體類型,因?yàn)楝F(xiàn)在有兩種類型,要合為一種類型,就需要?dú)w集,使用枚舉enum
來(lái)定義可能的類型:
#[derive(Deserialize, Serialize, Debug)] #[serde(untagged)] enum Animal { Dog(Dog), Cat(Cat), }
對(duì)于JSON
格式和rust
結(jié)構(gòu)體的互相轉(zhuǎn)換,可以使用serde
庫(kù)。也正好利用JSON
轉(zhuǎn)結(jié)構(gòu)體這一過(guò)程,利用轉(zhuǎn)換機(jī)制來(lái)實(shí)現(xiàn)動(dòng)態(tài)創(chuàng)建對(duì)象。
安裝相關(guān)的庫(kù):
cargo add serde serde_derive serde_json
我們定義一個(gè)JSON格式數(shù)據(jù),使用serde
庫(kù)進(jìn)行反序列化,并使用match
進(jìn)行匹配:
fn main() { let data = r#" { "name":"admin", "age":2 } "#; let animal = serde_json::from_str(data).unwrap(); match animal { Animal::Dog(dog) => { dog.say(); } Animal::Cat(cat) => { cat.say(); } }; }
測(cè)試運(yùn)行,正常輸出了cat say wangwang
。我們修改JSON格式數(shù)據(jù)
let data = r#" { "name":"admin", "work":"play" } "#;
測(cè)試運(yùn)行,正常輸出了dog say wangwang
,說(shuō)明沒(méi)有邏輯沒(méi)有問(wèn)題。
待優(yōu)化的地方在于我們使用了match
,如果我們需要在多個(gè)地方使用animal
,那么這段匹配邏輯就無(wú)處不在了。當(dāng)有很多方法時(shí),無(wú)法控制具體調(diào)用哪個(gè)方法,就需要不停的去匹配。
我們可以將它們需要調(diào)用公共方法在枚舉類型Animal
定義一下,內(nèi)部邏輯根據(jù)不同類型在調(diào)用各自的方法。
impl Animal { fn say(&self) { match self { Animal::Cat(cat) => cat.say(), Animal::Dog(dog) => dog.say(), } } }
為Animal
定義公共方法say
,然后在序列化JSON數(shù)據(jù)格式時(shí),我們必須要指定數(shù)據(jù)類型:
fn main() { let data = r#" { "name":"admin", "age":2, "work":"play" } "#; let animal: Animal = serde_json::from_str(data).unwrap(); animal.say(); }
明確指定了animal: Animal
,因?yàn)闆](méi)有其他邏輯幫助rust推斷出具體的類型是什么。也可以這么寫(xiě)let animal = serde_json::from_str::<Animal>(data).unwrap();
注意
需要注意的是:匹配的不同對(duì)象結(jié)構(gòu)體的字段不能一致,否則會(huì)匹配到枚舉的第一個(gè);如果出現(xiàn)包含的情況,我們需要把被包含的結(jié)構(gòu)體放在前面。
比如小貓也有work
字段了:
#[derive(Deserialize, Serialize, Debug)] struct Cat { name: String, age: i32, work: String, }
這是我們?cè)偃テヅ銳SON格式數(shù)據(jù),因?yàn)閿?shù)據(jù)里有age
,我們希望的是匹配小貓Cat
,但是它里面完全包含了小狗的字段Dog
,而且枚舉Animal
種小狗在前,所以會(huì)直接匹配小狗:
let data = r#" { "name":"admin", "age":2, "work":"play" } "#;
這樣達(dá)不到我們想要的結(jié)果,所以需要注意調(diào)整枚舉值的順序,可以將復(fù)雜數(shù)據(jù)結(jié)構(gòu)放到前面。將Cat
放到前面就可以正常工作了。
#[derive(Deserialize, Serialize, Debug)] #[serde(untagged)] enum Animal { Cat(Cat) Dog(Dog), }
動(dòng)態(tài)類型匹配
上一個(gè)方式是我們拿到了具體對(duì)象的JSON數(shù)據(jù),然后通過(guò)序列化,獲取到對(duì)應(yīng)的對(duì)象實(shí)例。如果我們只知道某個(gè)類型,需要根據(jù)類型初始化具體實(shí)例對(duì)象。
我們枚舉實(shí)例對(duì)象的類型,定義字符串轉(zhuǎn)枚舉類型的方法:
#[derive(Deserialize, Serialize, Debug)] #[serde(untagged)] enum AnimalType { Dog, Cat } impl AnimalType { fn str_to_animal_type(str: &str) -> AnimalType { match str { "dog" => AnimalType::Dog, "cat" => AnimalType::Cat, _ => panic!("unknown type"), } } }
調(diào)用AnimalType
獲取到枚舉類型,然后通過(guò)匹配類型來(lái)實(shí)例化對(duì)象,這跟上面的序列化JSON
格式后續(xù)處理方式一致。
fn main() { let names = "dog"; match AnimalType::str_to_animal_type(names) { AnimalType::Dog => { let dog = Dog { name: "admin".to_string(), work: "play".to_string(), }; dog.say(); } AnimalType::Cat => { let cat = Cat { name: "admin".to_string(), age: 2, work: "play".to_string(), }; cat.say(); } } }
Trait 特質(zhì)
trait
是rust中特有的類型,它可以定義對(duì)象的行為,然后可以被其他對(duì)象實(shí)現(xiàn)。實(shí)現(xiàn)它的對(duì)象可以擁有相同的行為,但是可以擁有不同的內(nèi)部邏輯。
這可以保證我們?cè)趧?dòng)態(tài)獲取到不同的對(duì)象實(shí)例,調(diào)用它們的方法時(shí)保證方法存在。在創(chuàng)建動(dòng)態(tài)對(duì)象時(shí),因?yàn)椴恢艟唧w大小,需要使用Box<dyn Trait>
定義動(dòng)態(tài)對(duì)象。
trait AnimalTrait { fn say(&self); }
然后在各個(gè)類型中實(shí)現(xiàn)AnimalTrait
,并實(shí)現(xiàn)公共方法say
。
impl AnimalTrait for Dog { fn say(&self) { println!("{} say wangwang", self.name); } } impl AnimalTrait for Cat { fn say(&self) { println!("{} say miamiamia", self.name); } }
定義類型都實(shí)現(xiàn)AnimalTrait
的方法,就可以放心的使用Box<dyn AnimalTrait>
提供的動(dòng)態(tài)對(duì)象了。
impl AnimalType { fn str_to_animal(str: &str) -> Box<dyn AnimalTrait> { match str { "dog" => Box::new(Dog::new("admin".to_string(), "play".to_string())), "cat" => Box::new(Cat::new("test".to_string())), _ => panic!("unknown type"), } } }
方法str_to_animal
通過(guò)類型匹配獲取到對(duì)應(yīng)的實(shí)例對(duì)象,現(xiàn)在我們不需要再匹配里直接調(diào)用方法了。我們拿到動(dòng)態(tài)對(duì)象,想調(diào)用那個(gè)方法就用哪個(gè)。
fn main() { let names = "dog"; let animal = AnimalType::str_to_animal(names); animal.say(); }
這樣就很方便的進(jìn)行動(dòng)態(tài)對(duì)象的傳遞,我們不需要關(guān)心該調(diào)用哪個(gè)方法,是否需要導(dǎo)入指定的方法。rust通過(guò)Box<dyn AnimalTrait>
會(huì)自動(dòng)調(diào)用合適的實(shí)現(xiàn)。
From/Into 類型強(qiáng)轉(zhuǎn)
我們定義了AnimalTrait
規(guī)范了動(dòng)態(tài)對(duì)象的行為,它們?cè)趯?shí)現(xiàn)了AnimalTrait
后,就可以根據(jù)動(dòng)態(tài)對(duì)象調(diào)用它的公共方法了。
但在根據(jù)類型創(chuàng)建動(dòng)態(tài)對(duì)象時(shí),仍然定義了枚舉AnimalType
的方法str_to_animal
并調(diào)用從而匹配到對(duì)應(yīng)的動(dòng)態(tài)對(duì)象。
我們還可以使用From
trait,通過(guò)讓AnimalTrait
實(shí)現(xiàn)From
trait,從而直接使用into
方法讓字符串類型轉(zhuǎn)為動(dòng)態(tài)對(duì)象。
impl From<&str> for Box<dyn AnimalTrait> { fn from(value: &str) -> Self { match value { "dog" => Box::new(Dog::new("admin".to_string(), "play".to_string())), "cat" => Box::new(Cat::new("test".to_string())), _ => panic!("unknown type"), } } }
這樣的實(shí)現(xiàn)可以減少在創(chuàng)建動(dòng)態(tài)對(duì)象時(shí)的顯示函數(shù)調(diào)用,我們?cè)谑褂玫臅r(shí)候直接調(diào)用into()
方法即可:
fn main{ let dog: Box<dyn AnimalTrait> = "dog".into(); dog.say(); }
HashMap映射類型
以上實(shí)現(xiàn)方案難免都使用了match
進(jìn)行匹配,而我們?cè)谥罢f(shuō)的映射對(duì)象的實(shí)現(xiàn),則可以避免match
的匹配。
通過(guò)HashMap
初始化類型映射結(jié)構(gòu)體對(duì)象,在使用時(shí)通過(guò)自定義方法get
傳入指定的類型,得到動(dòng)態(tài)類型。
struct AnimalFactory { map: HashMap<String, Box<dyn Fn() -> Box<dyn AnimalTrait>>>, }
我們定義了一個(gè)結(jié)構(gòu)體AnimalFactory
,其中包含一個(gè)HashMap
類型的字段map
,用于存儲(chǔ)類型與創(chuàng)建函數(shù)的映射關(guān)系。
注意到HashMap
的值是一個(gè)閉包函數(shù)而不是直接動(dòng)態(tài)類型,如果直接定義HashMap<String, Box<dyn AnimalTrait>>
,我們?cè)诔跏蓟瘯r(shí)就必須實(shí)例化創(chuàng)建對(duì)象實(shí)例,這就導(dǎo)致具體對(duì)象的實(shí)例只有一個(gè)而避免不了處理所有權(quán)的問(wèn)題。如果我們需要傳遞所有權(quán),就必須使用Arc
了。
定義了工廠結(jié)構(gòu)體AnimalFactory
,定義初始化函數(shù)new
:
impl AnimalFactory { fn new() -> Self { map.insert( "dog".to_string(), Box::new(|| { Box::new(Dog::new("admin".to_string(), "play".to_string())) as Box<dyn AnimalTrait> }) as Box<dyn Fn() -> Box<dyn AnimalTrait>>, ); map.insert( "cat".to_string(), Box::new(|| Box::new(Cat::new("test".to_string()))), ); AnimalFactory { map } } }
由于HashMap
需要定義具體的類型,我們?cè)诓迦腩愋?code>Dog時(shí)無(wú)法匹配定義的Box<dyn Fn() -> Box<dyn AnimalTrait>>
導(dǎo)致報(bào)錯(cuò),這就需要我們手動(dòng)強(qiáng)轉(zhuǎn)類型。
為了簡(jiǎn)化類型書(shū)寫(xiě),我們定義一個(gè)類型替代:
type AnimalDynType = Box<dyn Fn() -> Box<dyn AnimalTrait>>;
我們已經(jīng)初始化了映射表,定義根據(jù)具體類型獲取動(dòng)態(tài)對(duì)象的方法:
impl AnimalFactory { fn get(&self, name: &str) -> Box<dyn AnimalTrait> { match self.map.get(name) { Some(create_fn) => create_fn(), None => panic!("not found"), } } }
在使用時(shí),首先創(chuàng)建一個(gè)AnimalFactory對(duì)象,然后調(diào)用get方法,傳入具體的類型名稱,即可獲取對(duì)應(yīng)的動(dòng)態(tài)對(duì)象。
fn main() { let animal = AnimalFactory::new(); let dog = animal.get_animal("dog"); dog.say(); }
最后
這幾種實(shí)現(xiàn)方式都有一定的使用場(chǎng)景,根據(jù)實(shí)際需求選擇合適的方式。
到此這篇關(guān)于Rust中實(shí)例化動(dòng)態(tài)對(duì)象的示例詳解的文章就介紹到這了,更多相關(guān)Rust實(shí)例化動(dòng)態(tài)對(duì)象內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Rust語(yǔ)言的新手了解和學(xué)習(xí)入門(mén)啟蒙教程
這篇文章主要介紹了rust的特點(diǎn)、安裝、項(xiàng)目結(jié)構(gòu)、IDE環(huán)境配置、代碼運(yùn)行,講解了如何安裝Rust編譯器,創(chuàng)建和運(yùn)行第一個(gè)Rust程序,并對(duì)Rust語(yǔ)言的特點(diǎn)和優(yōu)勢(shì)作了說(shuō)明,包括內(nèi)存安全、高效性能、并發(fā)性、社區(qū)支持和統(tǒng)一包管理等,是新手了解和學(xué)習(xí)Rust語(yǔ)言的啟蒙教程2024-12-12深入探究在Rust中函數(shù)、方法和關(guān)聯(lián)函數(shù)有什么區(qū)別
在 Rust 中,函數(shù)、方法和關(guān)聯(lián)函數(shù)都是用來(lái)封裝行為的,它們之間的區(qū)別主要在于它們的定義和調(diào)用方式,本文將通過(guò)一個(gè)簡(jiǎn)單的rust代碼示例來(lái)給大家講講Rust中函數(shù)、方法和關(guān)聯(lián)函數(shù)區(qū)別,需要的朋友可以參考下2023-08-08在Rust應(yīng)用中訪問(wèn).ini格式的配置文件方式
Rust應(yīng)用中訪問(wèn).ini格式的配置文件,可以使用ini或config庫(kù),以ini庫(kù)為例,在Cargo.toml中添加依賴,然后在代碼中讀取和解析ini文件,確保配置文件路徑正確,使用section和get方法訪問(wèn)配置值2025-02-02從零開(kāi)始使用Rust編寫(xiě)nginx(TLS證書(shū)快過(guò)期了)
wmproxy已用Rust實(shí)現(xiàn)http/https代理,?socks5代理,?反向代理,?負(fù)載均衡,?靜態(tài)文件服務(wù)器,websocket代理,四層TCP/UDP轉(zhuǎn)發(fā),內(nèi)網(wǎng)穿透等,本文給大家介紹從零開(kāi)始使用Rust編寫(xiě)nginx(TLS證書(shū)快過(guò)期了),感興趣的朋友一起看看吧2024-03-03Rust語(yǔ)言之結(jié)構(gòu)體和枚舉的用途與高級(jí)功能詳解
Rust 是一門(mén)注重安全性和性能的現(xiàn)代編程語(yǔ)言,其中結(jié)構(gòu)體和枚舉是其強(qiáng)大的數(shù)據(jù)類型之一,了解結(jié)構(gòu)體和枚舉的概念及其高級(jí)功能,將使你能夠更加靈活和高效地處理數(shù)據(jù),本文將深入探討 Rust 中的結(jié)構(gòu)體和枚舉,并介紹它們的用途和高級(jí)功能2023-10-10