How do I dynamically create a struct from a string from input? - rust

Given a situation where we receive inputs for some nodes type like 'nodeA' or 'nodeB', and we want to initialize structs with that same input. Is it possible without a gigantic switch block? These structs share similar behaviour (using a Trait for that) but with some differences.
pub trait Executable {
fn run(&self);
}
pub struct NodeA {}
impl Executable for NodeA {
fn run(&self) {}
}
pub struct NodeB {}
impl Executable for NodeB {
fn run(&self) {}
}
Flow:
User inputs 'nodeA'
Program initializes struct nodeA with some data
User inputs 'nodeB'
Program initializes struct nodeB with some data
...
To specify better, the final use case is reading a JSON file with all the nodes and respective params to be instantiated. Some of those nodes can come from external plugins, so the number of existing nodes can become very big.

For smaller, static number of nodes, I think a match - case construct is perfectly fine.
But if you have a larger number of nodes, or the available nodes is dynamically changing, I would implement something like this:
pub trait Executable {
fn run(&self);
}
pub struct NodeA {}
impl Executable for NodeA {
fn run(&self) {
println!("NodeA::run()");
}
}
pub struct NodeB {}
impl Executable for NodeB {
fn run(&self) {
println!("NodeB::run()");
}
}
pub trait Matcher {
fn try_match(&self, s: &str) -> Option<Box<dyn Executable>>;
}
pub struct NodeAMatcher;
pub struct NodeBMatcher;
impl Matcher for NodeAMatcher {
fn try_match(&self, s: &str) -> Option<Box<dyn Executable>> {
(s == "NodeA").then(|| Box::new(NodeA {}) as Box<dyn Executable>)
}
}
impl Matcher for NodeBMatcher {
fn try_match(&self, s: &str) -> Option<Box<dyn Executable>> {
(s == "NodeB").then(|| Box::new(NodeB {}) as Box<dyn Executable>)
}
}
struct MatcherRegistry {
matchers: Vec<Box<dyn Matcher>>,
}
impl MatcherRegistry {
fn new() -> Self {
Self { matchers: vec![] }
}
fn register_matcher(&mut self, matcher: impl Matcher + 'static) {
self.matchers.push(Box::new(matcher));
}
fn try_get_node(&self, s: &str) -> Option<Box<dyn Executable>> {
self.matchers
.iter()
.filter_map(|matcher| matcher.try_match(s))
.next()
}
fn try_execute(&self, s: &str) {
if let Some(node) = self.try_get_node(s) {
node.run();
} else {
println!("'{}' not found.", s);
}
}
}
fn main() {
let mut registry = MatcherRegistry::new();
registry.register_matcher(NodeAMatcher);
registry.register_matcher(NodeBMatcher);
registry.try_execute("NodeA");
registry.try_execute("NodeB");
registry.try_execute("NodeC");
}
NodeA::run()
NodeB::run()
'NodeC' not found.
Here, you have a factory pattern.
The structs NodeAMatcher and NodeBMatcher are factories for NodeA and NodeB. They can check if the input matches, and then create an Executable object.
Then, you collect all possible factories (or Matchers here) in a registry, here called MatcherRegistry. You can then, at runtime, add or remove matchers as you wish.
Of course, if you don't need to create a new object every time and the act of executing doesn't consume it, you can reduce the complexity a little by bypassing the factory pattern:
use std::collections::HashMap;
pub trait Executable {
fn run(&self);
}
pub struct NodeA {}
impl Executable for NodeA {
fn run(&self) {
println!("NodeA::run()");
}
}
pub struct NodeB {}
impl Executable for NodeB {
fn run(&self) {
println!("NodeB::run()");
}
}
struct ExecutableRegistry {
executables: HashMap<&'static str, Box<dyn Executable>>,
}
impl ExecutableRegistry {
fn new() -> Self {
Self {
executables: HashMap::new(),
}
}
fn register_executable(
&mut self,
command: &'static str,
executable: impl Executable + 'static,
) {
self.executables.insert(command, Box::new(executable));
}
fn try_execute(&self, s: &str) {
if let Some(node) = self.executables.get(s) {
node.run();
} else {
println!("'{}' not found.", s);
}
}
}
fn main() {
let mut registry = ExecutableRegistry::new();
registry.register_executable("NodeA", NodeA {});
registry.register_executable("NodeB", NodeB {});
registry.try_execute("NodeA");
registry.try_execute("NodeB");
registry.try_execute("NodeC");
}
Of course there exists a large mount of other variations of the same patterns. Which one you implement is up to you and your usecase.

Related

What pattern to utilize to use a Vec of differing nested generic types/trait objects?

I'm trying to implement a pattern where different Processors can dictate the input type they take and produce a unified output (currently a fixed type, but I'd like to get it generic once this current implementation is working).
Below is a minimal example:
use std::convert::From;
use processor::NoOpProcessor;
use self::{
input::{Input, InputStore},
output::UnifiedOutput,
processor::{MultiplierProcessor, Processor, StringProcessor},
};
mod input {
use std::collections::HashMap;
#[derive(Debug)]
pub struct Input<T>(pub T);
#[derive(Default)]
pub struct InputStore(HashMap<String, String>);
impl InputStore {
pub fn insert<K, V>(mut self, key: K, value: V) -> Self
where
K: ToString,
V: ToString,
{
let key = key.to_string();
let value = value.to_string();
self.0.insert(key, value);
self
}
pub fn get<K, V>(&self, key: K) -> Option<Input<V>>
where
K: ToString,
for<'a> &'a String: Into<V>,
{
let key = key.to_string();
self.0.get(&key).map(|value| Input(value.into()))
}
}
}
mod processor {
use super::{input::Input, output::UnifiedOutput};
use super::I32Input;
pub struct NoOpProcessor;
pub trait Processor {
type I;
fn process(&self, input: &Input<Self::I>) -> UnifiedOutput;
}
impl Processor for NoOpProcessor {
type I = I32Input;
fn process(&self, input: &Input<Self::I>) -> UnifiedOutput {
UnifiedOutput(input.0 .0)
}
}
pub struct MultiplierProcessor(pub i32);
impl Processor for MultiplierProcessor {
type I = I32Input;
fn process(&self, input: &Input<Self::I>) -> UnifiedOutput {
UnifiedOutput(input.0 .0 * self.0)
}
}
pub struct StringProcessor;
impl Processor for StringProcessor {
type I = String;
fn process(&self, input: &Input<Self::I>) -> UnifiedOutput {
UnifiedOutput(input.0.parse().unwrap())
}
}
}
mod output {
#[derive(Debug)]
pub struct UnifiedOutput(pub i32);
}
pub fn main() {
let input_store = InputStore::default()
.insert("input_a", 123)
.insert("input_b", 567)
.insert("input_c", "789");
let processors = {
let mut labelled_processors = Vec::new();
// let mut labelled_processors: Vec<LabelledProcessor<Input<>>> = Vec::new(); // What's the correct type?
labelled_processors.push(LabelledProcessor("input_a", Box::new(NoOpProcessor)));
labelled_processors.push(LabelledProcessor(
"input_b",
Box::new(MultiplierProcessor(3)),
));
// labelled_processors.push(LabelledProcessor("input_c", Box::new(StringProcessor)));
labelled_processors
};
for processor in processors {
let output = retrieve_input_and_process(&input_store, processor);
println!("{:?}", output);
}
}
#[derive(Debug)]
pub struct I32Input(pub i32);
impl From<&String> for I32Input {
fn from(s: &String) -> Self {
Self(s.parse().unwrap())
}
}
struct LabelledProcessor<I>(&'static str, Box<dyn Processor<I = I>>)
where
for<'a> &'a String: Into<I>;
fn retrieve_input_and_process<T>(
store: &InputStore,
processor: LabelledProcessor<T>,
) -> UnifiedOutput
where
for<'a> &'a String: Into<T>,
{
let input = store.get(processor.0).unwrap();
processor.1.process(&input)
}
When // labelled_processors.push(LabelledProcessor("input_c", Box::new(StringProcessor))); is uncommented, I get the below compilation error:
error[E0271]: type mismatch resolving `<attempt2::processor::StringProcessor as attempt2::processor::Processor>::I == attempt2::I32Input`
--> src/attempt2.rs:101:63
|
101 | labelled_processors.push(LabelledProcessor("input_c", Box::new(StringProcessor)));
| ^^^^^^^^^^^^^^^^^^^^^^^^^ type mismatch resolving `<attempt2::processor::StringProcessor as attempt2::processor::Processor>::I == attempt2::I32Input`
|
note: expected this to be `attempt2::I32Input`
--> src/attempt2.rs:75:18
|
75 | type I = String;
| ^^^^^^
= note: required for the cast from `attempt2::processor::StringProcessor` to the object type `dyn attempt2::processor::Processor<I = attempt2::I32Input>`
I think I've learnt enough to "get" what the issue is - the labelled_processors vec expects all its items to have the same type. My problem is I'm unsure how to rectify this. I've tried to leverage dynamic dispatch more (for example changing LabelledProcessor to struct LabelledProcessor(&'static str, Box<dyn Processor<dyn Input>>);). However these changes spiral to their own issues with the type system too.
Other answers I've found online generally don't address this level of complexity with respect to the nested generics/traits - stopping at 1 level with the answer being let vec_x: Vec<Box<dyn SomeTrait>> .... This makes me wonder if there's an obvious answer that can be reached that I've just missed or if there's a whole different pattern I should be employing instead to achieve this goal?
I'm aware of potentially utilizing enums as wel, but that would mean all usecases would need to be captured within this module and it may not be able to define inputs/outputs/processors in external modules.
A bit lost at this point.
--- EDIT ---
Some extra points:
This is just an example, so things like InputStore basically converting everything to String is just an implementation detail. It's mainly to symbolize the concept of "the type needs to comply with some trait to be accepted", I just chose String for simplicity.
One possible solution would be to make retrieve_input_and_process a method of LabelledProcessor, and then hide the type behind a trait:
use std::convert::From;
use processor::NoOpProcessor;
use self::{
input::InputStore,
output::UnifiedOutput,
processor::{MultiplierProcessor, Processor, StringProcessor},
};
mod input {
use std::collections::HashMap;
#[derive(Debug)]
pub struct Input<T>(pub T);
#[derive(Default)]
pub struct InputStore(HashMap<String, String>);
impl InputStore {
pub fn insert<K, V>(mut self, key: K, value: V) -> Self
where
K: ToString,
V: ToString,
{
let key = key.to_string();
let value = value.to_string();
self.0.insert(key, value);
self
}
pub fn get<K, V>(&self, key: K) -> Option<Input<V>>
where
K: ToString,
for<'a> &'a str: Into<V>,
{
let key = key.to_string();
self.0.get(&key).map(|value| Input(value.as_str().into()))
}
}
}
mod processor {
use super::{input::Input, output::UnifiedOutput};
use super::I32Input;
pub struct NoOpProcessor;
pub trait Processor {
type I;
fn process(&self, input: &Input<Self::I>) -> UnifiedOutput;
}
impl Processor for NoOpProcessor {
type I = I32Input;
fn process(&self, input: &Input<Self::I>) -> UnifiedOutput {
UnifiedOutput(input.0 .0)
}
}
pub struct MultiplierProcessor(pub i32);
impl Processor for MultiplierProcessor {
type I = I32Input;
fn process(&self, input: &Input<Self::I>) -> UnifiedOutput {
UnifiedOutput(input.0 .0 * self.0)
}
}
pub struct StringProcessor;
impl Processor for StringProcessor {
type I = String;
fn process(&self, input: &Input<Self::I>) -> UnifiedOutput {
UnifiedOutput(input.0.parse().unwrap())
}
}
}
mod output {
#[derive(Debug)]
pub struct UnifiedOutput(pub i32);
}
pub fn main() {
let input_store = InputStore::default()
.insert("input_a", 123)
.insert("input_b", 567)
.insert("input_c", "789");
let processors = {
let mut labelled_processors: Vec<Box<dyn LabelledProcessorRef>> = Vec::new();
labelled_processors.push(Box::new(LabelledProcessor(
"input_a",
Box::new(NoOpProcessor),
)));
labelled_processors.push(Box::new(LabelledProcessor(
"input_b",
Box::new(MultiplierProcessor(3)),
)));
labelled_processors.push(Box::new(LabelledProcessor(
"input_c",
Box::new(StringProcessor),
)));
labelled_processors
};
for processor in processors {
let output = processor.retrieve_input_and_process(&input_store);
println!("{:?}", output);
}
}
#[derive(Debug)]
pub struct I32Input(pub i32);
impl From<&str> for I32Input {
fn from(s: &str) -> Self {
Self(s.parse().unwrap())
}
}
struct LabelledProcessor<I>(&'static str, Box<dyn Processor<I = I>>);
impl<I> LabelledProcessorRef for LabelledProcessor<I>
where
for<'a> &'a str: Into<I>,
{
fn retrieve_input_and_process(&self, store: &InputStore) -> UnifiedOutput {
let input = store.get(self.0).unwrap();
self.1.process(&input)
}
}
trait LabelledProcessorRef {
fn retrieve_input_and_process(&self, store: &InputStore) -> UnifiedOutput;
}
UnifiedOutput(123)
UnifiedOutput(1701)
UnifiedOutput(789)

How to use the typestate pattern in other struct

I want to use the typestate pattern to define several states that allow some exclusive operations on each of them.
I'm using traits instead of an enum to allow further customizations.
So, I'm able to use this pattern until I try to include it inside a struct (the Session part) that is mutated when files are added, changed or removed.
trait IssueState {}
struct Open;
impl IssueState for Open {}
struct WIP {
elapsed_time: u32,
}
impl IssueState for WIP {}
struct Closed {
elapsed_time: u32,
}
impl IssueState for Closed {}
struct Issue<T: IssueState + ?Sized> {
state: Box<T>,
comments: Vec<String>,
}
impl<T: IssueState> Issue<T> {
pub fn comment<S: Into<String>>(&mut self, comment: S) -> &mut Self {
self.comments.push(comment.into());
self
}
}
impl Issue<Open> {
pub fn new() -> Self {
Self {
state: Box::new(Open),
comments: vec![],
}
}
pub fn start(self) -> Issue<WIP> {
Issue {
state: Box::new(WIP { elapsed_time: 0 }),
comments: self.comments,
}
}
}
impl Issue<WIP> {
pub fn work(&mut self, time: u32) -> &mut Self {
self.state.elapsed_time += time;
self
}
pub fn done(self) -> Issue<Closed> {
let elapsed_time = self.state.elapsed_time;
Issue {
state: Box::new(Closed { elapsed_time }),
comments: self.comments,
}
}
}
impl Issue<Closed> {
pub fn elapsed(&self) -> u32 {
self.state.elapsed_time
}
}
struct Session<T: IssueState> {
user: String,
current_issue: Issue<T>,
}
impl<T: IssueState> Session<T> {
pub fn new<S: Into<String>>(user: S, issue: Issue<T>) -> Self {
Self {
user: user.into(),
current_issue: issue,
}
}
pub fn comment<S: Into<String>>(&mut self, comment: S) {
self.current_issue.comment(comment);
}
}
impl Session<WIP> {
pub fn work(&mut self, time: u32) {
self.current_issue.work(time);
}
}
trait Watcher {
fn watch_file_create(&mut self);
fn watch_file_change(&mut self);
fn watch_file_delete(&mut self);
}
impl<T: IssueState> Watcher for Session<T> {
fn watch_file_create(&mut self) {
self.current_issue = Issue::<Open>::new();
}
fn watch_file_change(&mut self) {}
fn watch_file_delete(&mut self) {}
}
fn main() {
let open = Issue::<Open>::new();
let mut wip = open.start();
wip.work(10).work(30).work(60);
let closed = wip.done();
println!("Elapsed {}", closed.elapsed());
let mut session = Session::new("Reviewer", closed);
session.comment("It is OK");
session.watch_file_create();
}
Rust Playground (original)
Rust Playground (edited)
What can I do to fix the problems?
Is the typestate pattern limited to only some situations that do not depend a lot on external events? I mean, I'm trying to use it for processing events, but is it a dead end?, why?
Your Session has a Issue<dyn IssueState> member, but you want to implement its work method by calling Issue<WIP>'s work method. The compiler complains, because an Issue<dyn IssueState> is not (necessarily) a Issue<WIP> and so does not implement that method.

Allowing extension (beyond the crate) of implementation with event loop

Within the crate we can happily do something like this:
mod boundary {
pub struct EventLoop;
impl EventLoop {
pub fn run(&self) {
for _ in 0..2 {
self.handle("bundled");
self.foo();
}
}
pub fn handle(&self, message: &str) {
println!("{} handling", message)
}
}
pub trait EventLoopExtend {
fn foo(&self);
}
}
use boundary::EventLoopExtend;
impl EventLoopExtend for boundary::EventLoop {
fn foo(&self) {
self.handle("extended")
}
}
fn main() {
let el = boundary::EventLoop{};
el.run();
}
But if mod boundary were a crate boundary we get error[E0117]: only traits defined in the current crate can be implemented for arbitrary types.
I gather that a potential solution to this could be the New Type idiom, so something like this:
mod boundary {
pub struct EventLoop;
impl EventLoop {
pub fn run(&self) {
for _ in 0..2 {
self.handle("bundled");
self.foo();
}
}
pub fn handle(&self, message: &str) {
println!("{} handling", message)
}
}
pub trait EventLoopExtend {
fn foo(&self);
}
impl EventLoopExtend for EventLoop {
fn foo(&self) {
self.handle("unimplemented")
}
}
}
use boundary::{EventLoop, EventLoopExtend};
struct EventLoopNewType(EventLoop);
impl EventLoopExtend for EventLoopNewType {
fn foo(&self) {
self.0.handle("extended")
}
}
fn main() {
let el = EventLoopNewType(EventLoop {});
el.0.run();
}
But then the problem here is that the extended trait behaviour isn't accessible from the underlying EventLoop instance.
I'm still quite new to Rust, so I'm sure I'm missing something obvious, I wouldn't be surprised if I need to take a completely different approach.
Specifically in my case, the event loop is actually from wgpu, and I'm curious if it's possible to build a library where end users can provide their own "render pass" stage.
Thanks to #AlexN's comment I dug deeper into the Strategy Pattern and found a solution:
mod boundary {
pub struct EventLoop<'a, T: EventLoopExtend> {
extension: &'a T
}
impl<'a, T: EventLoopExtend> EventLoop<'a, T> {
pub fn new(extension: &'a T) -> Self {
Self { extension }
}
pub fn run(&self) {
for _ in 0..2 {
self.handle("bundled");
self.extension.foo(self);
}
}
pub fn handle(&self, message: &str) {
println!("{} handling", message)
}
}
pub trait EventLoopExtend {
fn foo<T: EventLoopExtend>(&self, el: &EventLoop<T>) {
el.handle("unimplemented")
}
}
}
use boundary::{EventLoop, EventLoopExtend};
struct EventLoopExtension;
impl EventLoopExtend for EventLoopExtension {
fn foo<T: EventLoopExtend>(&self, el: &EventLoop<T>) {
el.handle("extended")
}
}
fn main() {
let el = EventLoop::new(&EventLoopExtension {});
el.run();
}
The basic idea is to use generics with a trait bound. I think the first time I looked into this approach I was worried about type recursion. But it turns out passing the EventLoop object as an argument to EventLoopExtend trait methods is perfectly reasonable.

The proper ownership for "caching proxy" in Rust?

I'd like to use Factory to build an object from the String and have multiple impls: 1) actual building and 2) caching (stores in-memory in HashMap). The problem is that in case #1 it have to pass the ownership and in case #2 HashMap owns the value and a reference can be returned only.
use std::collections::HashMap;
// product interface
pub trait TProduct {
fn get_title(&self) -> &String;
}
// and concrete impls
pub struct ConcreteProduct1 {
}
impl TProduct for ConcreteProduct1 {
// ...
}
pub struct ConcreteProduct2 {
}
impl TProduct for ConcreteProduct2 {
// ...
}
// factory interface
pub trait TProductFactory {
fn product_from_text(&mut self, text: String) -> Box<dyn TProduct>;
// QUESTION: should it be Box (required for ProductFactory) or &Box (required for ProductCachingProxy)?
}
// actual building factory
pub struct ProductFactory {
}
impl TProductFactory for ProductFactory {
fn product_from_text(&mut self, text: String) -> Box<dyn TProduct> {
//...
// depending on some conditions
Box::new(ConcreteProduct1::from_text(text)); // has to pass the ownership
// or
Box::new(ConcreteProduct2::from_text(text)); // has to pass the ownership
//...
}
}
// caching proxy
trait TProductCache {
fn put(&mut self, text: &String, product: Box<dyn TProduct>);
fn get(&self, text: &String) -> Option<&Box<dyn TProduct>>;
fn clear(&mut self);
}
struct InMemoryProductCache {
map: HashMap<String, Box<dyn TProduct>>
}
impl InMemoryProductCache {
fn new() -> Self {
return InMemoryProductCache {
map: HashMap::new()
}
}
}
impl TProductCache for InMemoryProductCache {
fn put(&mut self, text: &String, product: Box<dyn TProduct>) {
self.map.insert(text.to_string(), product);
}
fn get(&self, text: &String) -> Option<&Box<dyn TProduct>> {
return match self.map.get(text) {
Some(boxed_product) => Some(boxed_product), // have to pass a reference to let it still own the value
None => None
}
}
fn clear(&mut self) {
self.map.clear();
}
}
struct ProductCachingProxy {
product_factory: Box<dyn TProductFactory>,
cache: Box<dyn TProductCache>
}
impl ProductCachingProxy {
fn new_for_factory(product_factory: Box<dyn TProductFactory>, cache: Box<dyn TProductCache>) -> Self {
return ProductCachingProxy {
product_factory,
cache
}
}
}
impl TProductFactory for ProductCachingProxy {
fn product_from_text(&mut self, text: String) -> &Box<dyn TProduct> { // can't pass ownership
let boxed_product = match self.cache.get(&text) {
Some(found_boxed_product) => found_boxed_product,
_ => {
// delegate creation to wrapped TProductFactory impl (`product_factory`)
let boxed_product = self.product_factory.product_from_text(text.clone());
// ... and put to the cache
self.cache.put(&text, boxed_product);
&boxed_product
}
};
return boxed_product;
}
}
// QUESTION: should it be Box (required for ProductFactory) or &Box (required for ProductCachingProxy) to be returned from TProductFactory.fn product_from_text(&mut self, text: String) -> Box<dyn TProduct>; ?
If caching proxy to return a Box, how can it be created from a reference without copying/cloning (TProductCache.get(..))?
Replace Box with Rc (or Arc if you use threads). It provides shared ownership and suites both your cases with single signature. Another option is to use Cow that is a enum of owned and borrowed states.

Storing types in a HashMap to dynamically instantiate them

I am trying to store structs in a HashMap keyed by string so that I can later create new objects by string. Think of a REST API where clients can get the server to instantiate a specific object by supplying a name.
use std::collections::HashMap;
struct MyStruct;
impl MyStruct {
pub fn new() -> Self {
Self {}
}
}
struct MyOtherStruct;
impl MyOtherStruct {
pub fn new() -> Self {
Self {}
}
}
fn main() {
let mut h = HashMap::new();
h.insert("MyStruct", MyStruct);
h.insert("MyOtherStruct", MyOtherStruct);
// This is pseudo-code
let obj = h.get("MyStruct").unwrap()::new();
}
As I expected, this doesn't work due to syntax errors:
error: expected one of `.`, `;`, `?`, or an operator, found `::`
--> src/main.rs:25:41
|
25 | let obj = h.get("MyStruct").unwrap()::new();
| ^^ expected one of `.`, `;`, `?`, or an operator here
My second attempt was to store a reference to the new method of each struct instead of the types themselves.
use std::collections::HashMap;
struct MyStruct;
impl MyStruct {
pub fn new() -> Self {
Self {}
}
}
struct MyOtherStruct;
impl MyOtherStruct {
pub fn new() -> Self {
Self {}
}
}
fn main() {
let mut h = HashMap::new();
h.insert("MyStruct", &MyStruct::new);
h.insert("MyOtherStruct", &MyOtherStruct::new);
let obj = h.get("MyStruct").unwrap()();
}
This fails because the fn items have different types and can't be stored in the same HashMap:
error[E0308]: mismatched types
--> src/main.rs:22:31
|
22 | h.insert("MyOtherStruct", &MyOtherStruct::new);
| ^^^^^^^^^^^^^^^^^^^ expected fn item, found a different fn item
|
= note: expected type `&fn() -> MyStruct {MyStruct::new}`
found type `&fn() -> MyOtherStruct {MyOtherStruct::new}`
Since I'm pretty new to Rust, I'm out of ideas. How can I solve this problem?
This is ultimately fundamentally impossible. In Rust, local variables are stored on the stack, which means that they have to have a fixed size, known at compile time. Your construction requires the size of the value on the stack to be determined at runtime.
The closest alternative is to move to trait objects, which introduce a layer of indirection:
use std::collections::HashMap;
trait NewThing {
fn new(&self) -> Box<Thing>;
}
trait Thing {}
struct MyStruct;
impl NewThing for MyStruct {
fn new(&self) -> Box<Thing> {
Box::new(Self {})
}
}
impl Thing for MyStruct {}
struct MyOtherStruct;
impl NewThing for MyOtherStruct {
fn new(&self) -> Box<Thing> {
Box::new(Self {})
}
}
impl Thing for MyOtherStruct {}
fn main() {
let mut h: HashMap<_, Box<NewThing>> = HashMap::new();
h.insert("MyStruct", Box::new(MyStruct));
h.insert("MyOtherStruct", Box::new(MyOtherStruct));
let obj = h["MyStruct"].new();
}
You will find this pattern out in the world, such as in hyper's NewService.
what is [the value of &self of method new] when calling h["MyStruct"].new()
It's an instance of MyStruct or MyOtherStruct. The only reason that the same type can implement both traits is because there's no real unique state for the "factory" and the "instance". In more complicated implementations, these would be two different types.
Using the same type is common for such cases as sharing a reference-counted value.
See also:
Is it possible to have a constructor function in a trait?
Here is a more complex example of #Shepmaster's solution, using different types for Factories and the objects themselves:
use std::collections::HashMap;
trait NewThing {
fn new(&self) -> Box<Thing>;
}
trait Thing {
fn execute(&mut self);
}
// MyStruct
struct MyStructFactory;
impl NewThing for MyStructFactory {
fn new(&self) -> Box<Thing> {
Box::new(MyStruct {test: 12, name: "Test".into()})
}
}
struct MyStruct {
test: i32,
name: String
}
impl Thing for MyStruct {
fn execute(&mut self) {
self.test+=1;
println!("MyStruct {} {}", self.test, self.name);
}
}
// MyOtherStruct
struct MyOtherStructFactory;
impl NewThing for MyOtherStructFactory {
fn new(&self) -> Box<Thing> {
Box::new(MyOtherStruct {my_member: 1})
}
}
struct MyOtherStruct {
my_member: u32
}
impl Thing for MyOtherStruct {
fn execute(&mut self) { println!("MyOtherStruct.my_member: {}", self.my_member); }
}
fn main() {
let mut h: HashMap<_, Box<NewThing>> = HashMap::new();
h.insert("MyStruct", Box::new(MyStructFactory));
h.insert("MyOtherStruct", Box::new(MyOtherStructFactory));
h["MyStruct"].new().execute();
h["MyOtherStruct"].new().execute();
}
You could use std::any::Any to erase the type of the entry. They use Any::downcast<T> to check if the entry at the location matches your type, and get a Ok(Box<T>)

Resources