Reputation: 8311
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?
Upvotes: 4
Views: 2061
Reputation: 42716
You can consider using an enum
instead of dynamically dispatch it:
trait Abstract {
fn name(&self) -> &str;
fn new(name: &str) -> Option<AbstractVariant> {
match name {
"foo" => Some(AbstractVariant::Foo(Foo {})),
"bar" => Some(AbstractVariant::Bar(Bar {})),
"baz" => Some(AbstractVariant::Baz(Baz {})),
_ => None,
}
}
}
enum AbstractVariant {
Foo(Foo),
Bar(Bar),
Baz(Baz),
}
Upvotes: 3
Reputation: 10156
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.
Upvotes: 9
Reputation: 8514
fn new<T: Abstract>(…) -> T
means: new
will return some concrete T
that implements Abstract
, and whatever calls new
may decide which.fn new() -> impl Abstract
means: new
will return some concrete type of its choosing that implements Abstract, but whatever calls new
won't know which concrete type. (But it must still be some single concrete type decided at compile-time, it can't be "Maybe Foo
or maybe Baz
")If you absolutely want this, you can have a free function do your constructing:
trait Abstract {
fn name(&self) -> &str;
}
fn new_abstract(name: &str) -> Box<dyn Abstract> {
match name {
"foo" => Box::new(Foo{}),
"bar" => Box::new(Bar{}),
"baz" => Box::new(Baz{}),
_ => panic!("better not")
}
}
The new
function can't be part of Abstract
, because you would get into trouble with object safety. Essentially, if you want to return a Box<dyn …>
of some trait, all the methods on the trait must be usable in dynamic dispatch, but new
is not such a method.
Anyway, this is not a good idea. Adding a separate new
function to Foo
, Bar
, and Baz
is the Rust way to go.
Upvotes: 3