I'm just learning Rust, so maybe I just didn't get some concepts correctly.
I have a trait with some implementations:
trait Abstract {
fn name(&self) -> &str;
}
struct Foo {}
struct Bar {}
struct Baz {}
impl Abstract for Foo {
fn name(&self) -> &str { "foo" }
}
impl Abstract for Bar {
fn name(&self) -> &str { "bar" }
}
impl Abstract for Baz {
fn name(&self) -> &str { "baz" }
}
I want to add a static method to this trait to create some standard implementation by name:
trait Abstract {
fn new(name: &str) -> Self {
match name {
"foo" => Foo{},
"bar" => Bar{},
"baz" => Baz{},
}
}
fn name(&self) -> &str;
}
But this code doesn't compile because:
6 | fn new(name: &str) -> Self {
| ^^^^ doesn't have a size known at compile-time
I tried to use return fn new(name: &str) -> impl Abstract
and
fn new<T: Abstract>(name: &str) -> T
- but these variants doesn't work too with another errors.
What is the correct way exist of creating factory method for trait in Rust?
In Rust, every variable must be a single specific type. This is different to OO languages where a variable can have a type Foo
, and that means that the variable contains a Foo
, or any subclass of Foo
.
Rust doesn't support this. If a variable has a type Foo
, it must contain a Foo
(ignoring any unsafe concerns).
Rust also doesn't support using traits as types (without the dyn
keyword).
In your example, you have:
trait Abstract {
fn new(name: &str) -> Self {
// ...
}
}
The return type Self
here means "whatever type this trait is being implemented on". However, by providing a body in the trait definition, you're providing a default implementation, so this function should in theory apply to any type, and so the compiler has no information about the real concrete type Self
, and therefore the Sized
bound it not met (which in practice is very restrictive and probably not what you want).
If you want a function that takes a string and returns "some type T
that implements Abstract
", you could use a "trait object", which would look roughly like:
// outside trait definition
fn new_abstract(name: &str) -> Box<dyn Abstract> { // dyn keyword opts into dynamic dispatch with vtables
match name {
"foo" => Box::new(Foo {}),
// ...
}
}
However, I'd warn against this pattern. Dynamic dispatch has some runtime overhead, and prevents many compile-time optimizations. Instead, there may be a more "rusty" way of doing it, but it's hard to tell without more context.
In general, constructing a type based on the value of a string smells like a bit of an anti-pattern.