Rust中實例化動態(tài)對象的示例詳解
在功能開發(fā)中,動態(tài)創(chuàng)建或獲取某個對象的情況很多。在前端JS開發(fā)中,可以使用工廠函數(shù),通過給定的類型標(biāo)識創(chuàng)建不同的對象實例;還可以通過對象映射來實現(xiàn)動態(tài)創(chuàng)建對象。
在Rust中,我們也可以使用這兩種方式去創(chuàng)建對象實例,但實現(xiàn)書寫的方式可能略有不同;rust還可以通過序列化JSON數(shù)據(jù)時進(jìn)行枚舉類型匹配。
我們定義好需要測試的數(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
我們在拿到JSON格式數(shù)據(jù)進(jìn)行序列化時,在rust中是需要確定具體數(shù)據(jù)類型的,但是我們并不知道具體類型,因為現(xiàn)在有兩種類型,要合為一種類型,就需要歸集,使用枚舉enum來定義可能的類型:
#[derive(Deserialize, Serialize, Debug)]
#[serde(untagged)]
enum Animal {
Dog(Dog),
Cat(Cat),
}
對于JSON格式和rust 結(jié)構(gòu)體的互相轉(zhuǎn)換,可以使用serde庫。也正好利用JSON轉(zhuǎn)結(jié)構(gòu)體這一過程,利用轉(zhuǎn)換機(jī)制來實現(xiàn)動態(tài)創(chuàng)建對象。
安裝相關(guān)的庫:
cargo add serde serde_derive serde_json
我們定義一個JSON格式數(shù)據(jù),使用serde庫進(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();
}
};
}
測試運行,正常輸出了cat say wangwang。我們修改JSON格式數(shù)據(jù)
let data = r#"
{
"name":"admin",
"work":"play"
}
"#;
測試運行,正常輸出了dog say wangwang,說明沒有邏輯沒有問題。
待優(yōu)化的地方在于我們使用了match,如果我們需要在多個地方使用animal,那么這段匹配邏輯就無處不在了。當(dāng)有很多方法時,無法控制具體調(diào)用哪個方法,就需要不停的去匹配。
我們可以將它們需要調(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ù)據(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,因為沒有其他邏輯幫助rust推斷出具體的類型是什么。也可以這么寫let animal = serde_json::from_str::<Animal>(data).unwrap();
注意
需要注意的是:匹配的不同對象結(jié)構(gòu)體的字段不能一致,否則會匹配到枚舉的第一個;如果出現(xiàn)包含的情況,我們需要把被包含的結(jié)構(gòu)體放在前面。
比如小貓也有work字段了:
#[derive(Deserialize, Serialize, Debug)]
struct Cat {
name: String,
age: i32,
work: String,
}
這是我們再去匹配JSON格式數(shù)據(jù),因為數(shù)據(jù)里有age,我們希望的是匹配小貓Cat,但是它里面完全包含了小狗的字段Dog,而且枚舉Animal種小狗在前,所以會直接匹配小狗:
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),
}
動態(tài)類型匹配
上一個方式是我們拿到了具體對象的JSON數(shù)據(jù),然后通過序列化,獲取到對應(yīng)的對象實例。如果我們只知道某個類型,需要根據(jù)類型初始化具體實例對象。
我們枚舉實例對象的類型,定義字符串轉(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獲取到枚舉類型,然后通過匹配類型來實例化對象,這跟上面的序列化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中特有的類型,它可以定義對象的行為,然后可以被其他對象實現(xiàn)。實現(xiàn)它的對象可以擁有相同的行為,但是可以擁有不同的內(nèi)部邏輯。
這可以保證我們在動態(tài)獲取到不同的對象實例,調(diào)用它們的方法時保證方法存在。在創(chuàng)建動態(tài)對象時,因為不知掉具體大小,需要使用Box<dyn Trait>定義動態(tài)對象。
trait AnimalTrait {
fn say(&self);
}
然后在各個類型中實現(xiàn)AnimalTrait,并實現(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);
}
}定義類型都實現(xiàn)AnimalTrait的方法,就可以放心的使用Box<dyn AnimalTrait>提供的動態(tài)對象了。
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通過類型匹配獲取到對應(yīng)的實例對象,現(xiàn)在我們不需要再匹配里直接調(diào)用方法了。我們拿到動態(tài)對象,想調(diào)用那個方法就用哪個。
fn main() {
let names = "dog";
let animal = AnimalType::str_to_animal(names);
animal.say();
}
這樣就很方便的進(jìn)行動態(tài)對象的傳遞,我們不需要關(guān)心該調(diào)用哪個方法,是否需要導(dǎo)入指定的方法。rust通過Box<dyn AnimalTrait>會自動調(diào)用合適的實現(xiàn)。
From/Into 類型強(qiáng)轉(zhuǎn)
我們定義了AnimalTrait規(guī)范了動態(tài)對象的行為,它們在實現(xiàn)了AnimalTrait后,就可以根據(jù)動態(tài)對象調(diào)用它的公共方法了。
但在根據(jù)類型創(chuàng)建動態(tài)對象時,仍然定義了枚舉AnimalType的方法str_to_animal并調(diào)用從而匹配到對應(yīng)的動態(tài)對象。
我們還可以使用Fromtrait,通過讓AnimalTrait實現(xiàn)Fromtrait,從而直接使用into方法讓字符串類型轉(zhuǎn)為動態(tài)對象。
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"),
}
}
}
這樣的實現(xiàn)可以減少在創(chuàng)建動態(tài)對象時的顯示函數(shù)調(diào)用,我們在使用的時候直接調(diào)用into()方法即可:
fn main{
let dog: Box<dyn AnimalTrait> = "dog".into();
dog.say();
}
HashMap映射類型
以上實現(xiàn)方案難免都使用了match進(jìn)行匹配,而我們在之前說的映射對象的實現(xiàn),則可以避免match的匹配。
通過HashMap初始化類型映射結(jié)構(gòu)體對象,在使用時通過自定義方法get傳入指定的類型,得到動態(tài)類型。
struct AnimalFactory {
map: HashMap<String, Box<dyn Fn() -> Box<dyn AnimalTrait>>>,
}
我們定義了一個結(jié)構(gòu)體AnimalFactory,其中包含一個HashMap類型的字段map,用于存儲類型與創(chuàng)建函數(shù)的映射關(guān)系。
注意到HashMap的值是一個閉包函數(shù)而不是直接動態(tài)類型,如果直接定義HashMap<String, Box<dyn AnimalTrait>>,我們在初始化時就必須實例化創(chuàng)建對象實例,這就導(dǎo)致具體對象的實例只有一個而避免不了處理所有權(quá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需要定義具體的類型,我們在插入類型Dog時無法匹配定義的Box<dyn Fn() -> Box<dyn AnimalTrait>>導(dǎo)致報錯,這就需要我們手動強(qiáng)轉(zhuǎn)類型。
為了簡化類型書寫,我們定義一個類型替代:
type AnimalDynType = Box<dyn Fn() -> Box<dyn AnimalTrait>>;
我們已經(jīng)初始化了映射表,定義根據(jù)具體類型獲取動態(tài)對象的方法:
impl AnimalFactory {
fn get(&self, name: &str) -> Box<dyn AnimalTrait> {
match self.map.get(name) {
Some(create_fn) => create_fn(),
None => panic!("not found"),
}
}
}
在使用時,首先創(chuàng)建一個AnimalFactory對象,然后調(diào)用get方法,傳入具體的類型名稱,即可獲取對應(yīng)的動態(tài)對象。
fn main() {
let animal = AnimalFactory::new();
let dog = animal.get_animal("dog");
dog.say();
}
最后
這幾種實現(xiàn)方式都有一定的使用場景,根據(jù)實際需求選擇合適的方式。
到此這篇關(guān)于Rust中實例化動態(tài)對象的示例詳解的文章就介紹到這了,更多相關(guān)Rust實例化動態(tài)對象內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
深入探究在Rust中函數(shù)、方法和關(guān)聯(lián)函數(shù)有什么區(qū)別
在 Rust 中,函數(shù)、方法和關(guān)聯(lián)函數(shù)都是用來封裝行為的,它們之間的區(qū)別主要在于它們的定義和調(diào)用方式,本文將通過一個簡單的rust代碼示例來給大家講講Rust中函數(shù)、方法和關(guān)聯(lián)函數(shù)區(qū)別,需要的朋友可以參考下2023-08-08
Rust語言之結(jié)構(gòu)體和枚舉的用途與高級功能詳解
Rust 是一門注重安全性和性能的現(xiàn)代編程語言,其中結(jié)構(gòu)體和枚舉是其強(qiáng)大的數(shù)據(jù)類型之一,了解結(jié)構(gòu)體和枚舉的概念及其高級功能,將使你能夠更加靈活和高效地處理數(shù)據(jù),本文將深入探討 Rust 中的結(jié)構(gòu)體和枚舉,并介紹它們的用途和高級功能2023-10-10

