In functional development, there are many cases where an object is created or obtained dynamically. In front-end JS development, you can use factory functions to create different object instances through a given type identification; you can also create objects dynamically through object mapping.
existRust
In this article, we can also use these two methods to create object instances, but the implementation of writing may be slightly different;rust
It can also be serializedJSON
Enumeration type matching is performed when data is performed.
We define the data structure and method that needs to be tested. Puppies and kittens have their own fields and methods, they have the same fieldsname
, there is also the same methodsay
。
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", ); } } #[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", ); } }
Serialize serde
We're getting itJSON
When the format data is serialized,rust
The specific data type needs to be determined, but we do not know the specific type, because there are two types now. To combine into one type, we need to collect and use enumeration.enum
To define possible types:
#[derive(Deserialize, Serialize, Debug)] #[serde(untagged)] enum Animal { Dog(Dog), Cat(Cat), }
forJSON
Format andrust
The mutual conversion of structures can be usedserde
library. Just use itJSON
The process of transforming structures uses the transformation mechanism to achieve dynamic creation of objects.
Install related libraries:
cargo add serde serde_derive serde_json
We define a JSON format data, usingserde
The library is deserialized and usedmatch
Making a match:
fn main() { let data = r#" { "name":"admin", "age":2 } "#; let animal = serde_json::from_str(data).unwrap(); match animal { Animal::Dog(dog) => { (); } Animal::Cat(cat) => { (); } }; }
The test runs and the output is normalcat say wangwang
. We modify the JSON format data
let data = r#" { "name":"admin", "work":"play" } "#;
The test runs and the output is normaldog say wangwang
, it means there is no logic and no problem.
What is to be optimized is that we use itmatch
, if we need to use it in multiple placesanimal
, then this matching logic is everywhere. When there are many methods, you cannot control which method to call, so you need to constantly match it.
We can put them in the enumeration typeAnimal
Define that internal logic calls their own methods according to different types.
impl Animal { fn say(&self) { match self { Animal::Cat(cat) => (), Animal::Dog(dog) => (), } } }
forAnimal
Define public methodssay
, and then when serializing the JSON data format, we must specify the data type:
fn main() { let data = r#" { "name":"admin", "age":2, "work":"play" } "#; let animal: Animal = serde_json::from_str(data).unwrap(); (); }
It has been clearly specifiedanimal: Animal
, because there is no other logic to help rust infer what the specific type is. It can also be writtenlet animal = serde_json::from_str::<Animal>(data).unwrap();
Notice
It should be noted that the fields of the matching different object structures cannot be consistent, otherwise the first one of the enumeration will be matched; if there is a case of inclusion, we need to put the included structure in front.
For example, there are kittens, toowork
Fields are here:
#[derive(Deserialize, Serialize, Debug)] struct Cat { name: String, age: i32, work: String, }
This is how we match JSON format data, because there isage
, what we want is to match the kittenCat
, but it contains all the puppy fieldsDog
, and enumerationAnimal
Breeding puppies is the first, so they will match puppies directly:
let data = r#" { "name":"admin", "age":2, "work":"play" } "#;
This cannot achieve the result we want, so we need to pay attention to adjusting the order of enum values and putting complex data structures in front. WillCat
Put it in front and it will work normally.
#[derive(Deserialize, Serialize, Debug)] #[serde(untagged)] enum Animal { Cat(Cat) Dog(Dog), }
Dynamic type matching
The previous method is that we get the JSON data of the specific object, and then obtain the corresponding object instance through serialization. If we only know a certain type, we need to initialize the specific instance object according to the type.
We enumerate the type of the instance object and define the method of converting string to enumeration type:
#[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"), } } }
CallAnimalType
Get the enum type and instantiate the object by matching the type, which is the serialization aboveJSON
The format subsequent processing method is consistent.
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(), }; (); } AnimalType::Cat => { let cat = Cat { name: "admin".to_string(), age: 2, work: "play".to_string(), }; (); } } }
Trait trait
trait
It is a type unique to rust, which can define the behavior of an object and can then be implemented by other objects. The object that implements it can have the same behavior, but can have different internal logic.
This ensures that we dynamically obtain different object instances and ensure that the methods exist when calling their methods. When creating a dynamic object, you need to use it because you don’t know the specific size.Box<dyn Trait>
Define dynamic objects.
trait AnimalTrait { fn say(&self); }
Then implement it in various typesAnimalTrait
and implement public methodssay
。
impl AnimalTrait for Dog { fn say(&self) { println!("{} say wangwang", ); } } impl AnimalTrait for Cat { fn say(&self) { println!("{} say miamiamia", ); } }
Definition types are implementedAnimalTrait
You can use it with confidenceBox<dyn AnimalTrait>
The dynamic object is provided.
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"), } } }
methodstr_to_animal
The corresponding instance object is obtained through type matching. Now we no longer need to call the method directly in the match. We get the dynamic object and use which method we want to call.
fn main() { let names = "dog"; let animal = AnimalType::str_to_animal(names); (); }
This makes it very convenient to pass dynamic objects. We don’t need to care which method to call or whether we need to import the specified method. rust byBox<dyn AnimalTrait>
The appropriate implementation will be called automatically.
From/Into type strong rotation
We definedAnimalTrait
Standardize the behavior of dynamic objects, and they are implementing themAnimalTrait
After that, it can call its public method based on the dynamic object.
But when creating dynamic objects based on type, enums are still definedAnimalType
Methodstr_to_animal
and call to match the corresponding dynamic object.
We can also useFrom
trait, by letAnimalTrait
accomplishFrom
trait, so use it directlyinto
Method to convert string type into dynamic object.
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"), } } }
This implementation can reduce the display function calls when creating dynamic objects, which we call directly when usinginto()
Method:
fn main{ let dog: Box<dyn AnimalTrait> = "dog".into(); (); }
HashMap mapping type
All the above implementation solutions are inevitably usedmatch
Making matches, and the implementation of mapping objects we mentioned earlier can avoidmatch
match.
passHashMap
Initialize type map structure object, through custom methods when usingget
Pass in the specified type to get the dynamic type.
struct AnimalFactory { map: HashMap<String, Box<dyn Fn() -> Box<dyn AnimalTrait>>>, }
We define a structureAnimalFactory
, which contains oneHashMap
Fields of typemap
, used to store the mapping relationship between types and creation functions.
NotedHashMap
The value of the closure function is not a direct dynamic type, if defined directlyHashMap<String, Box<dyn AnimalTrait>>
,We must instantiate the object instance when initializing, which leads to only one instance of the specific object, and the issue of ownership cannot be avoided. If we need to pass ownership, we must useArc
Now.
Defines the factory structureAnimalFactory
, define the initialization functionnew
:
impl AnimalFactory { fn new() -> Self { ( "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>>, ); ( "cat".to_string(), Box::new(|| Box::new(Cat::new("test".to_string()))), ); AnimalFactory { map } } }
becauseHashMap
We need to define a specific type, we are inserting the typeDog
Unable to match the definedBox<dyn Fn() -> Box<dyn AnimalTrait>>
This leads to an error, which requires us to manually force the type.
To simplify type writing, we define a type substitution:
type AnimalDynType = Box<dyn Fn() -> Box<dyn AnimalTrait>>;
We have initialized the mapping table and defined a method to obtain dynamic objects based on specific types:
impl AnimalFactory { fn get(&self, name: &str) -> Box<dyn AnimalTrait> { match (name) { Some(create_fn) => create_fn(), None => panic!("not found"), } } }
When using it, first create an AnimalFactory object, then call the get method, pass in the specific type name, and get the corresponding dynamic object.
fn main() { let animal = AnimalFactory::new(); let dog = animal.get_animal("dog"); (); }
at last
These implementation methods have certain usage scenarios, and choose the appropriate method according to actual needs.
This is the end of this article about the detailed explanation of the examples of instantiating dynamic objects in Rust. For more relevant content on instantiating dynamic objects in Rust, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!