rustfactory-method

Static factory method for trait


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?


Solution

  • 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.