简体   繁体   中英

How do I construct any of potentially many structs that implement a trait?

I have created a small working example of a problem I'm having in Rust below:

trait TraitFoo {
    fn foo(&self) -> i32;
}

struct StructBar {
    var: i32,
}

impl TraitFoo for StructBar {
    fn foo(&self) -> i32 {
        self.var
    }
}

impl StructBar {
    fn new() -> StructBar {
        StructBar { var: 5 }
    }
}

struct FooHolder<T: TraitFoo> {
    myfoo: T,
}

impl<T: TraitFoo> FooHolder<T> {
    fn new() -> FooHolder<T> {
        FooHolder { myfoo: StructBar::new() }
    }
}

fn main() {
    let aaa = FooHolder::new();
}

This fails to compile with :

error[E0308]: mismatched types
  --> src/main.rs:27:9
   |
27 |         FooHolder { myfoo: StructBar::new() }
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected type parameter, found struct `StructBar`
   |
   = note: expected type `FooHolder<T>`
              found type `FooHolder<StructBar>`

I would like to be able to return any of potentially many structs that implement TraitFoo from the FooHolder::new() method. I'd like it to expect any T:TraitFoo as a return type instead of just StructBar in this case.

I have tried several things but something like moving new() into the trait wouldn't help me as new structs that implement TraitBar might take different arguments into new() .

You seem to be almost on the right track. Let's cover the essentials:

An implementation of a function fn new() -> FooHolder<T> cannot choose the type T . That is chosen by the context where it's called. So we cannot enforce, or always assume that T = StructBar .

Usually, you would do one of two things: provide a constructor interface to T in a trait implemented by T .

something like moving new() into the trait wouldn't help me as new structs that implement TraitBar might take different arguments into new().

Even if you could do that, how would the compiler know what arguments to expect in FooHolder::new() ? Variable number of arguments are out of your reach here (see How can I create a function with a variable number of arguments? ), so telling the compiler to accept a different number of arguments depending on T is not realistic. However, we can emulate that by having an associated type that defines construction parameters. In the following code, I took the liberty of making the identifiers more idiomatic ( Struct or Trait as prefixes only introduce noise).

trait Foo {
    type Params; // new type parameter

    fn new(params: Self::Params) -> Self; // new static method

    fn foo(&self) -> i32;
}

Our Bar would be defined like this:

struct Bar {
    var: i32,
}

impl Foo for Bar {
    type Params = i32;

    fn foo(&self) -> i32 {
        self.var
    }

    fn new(params: Self::Params) -> Self {
        Bar { var: params }
    }
}

And our FooHolder would now be able to construct a value of type T :

struct FooHolder<T: Foo> {
    myfoo: T,
}

impl<T: Foo> FooHolder<T> {
    fn new(params: T::Params) -> FooHolder<T> {
        FooHolder { myfoo : T::new(params) }
    }
}

Using FooHolder :

let aaa = FooHolder::<Bar>::new(5);

When no arguments are needed to construct T , we can rely on the Default trait:

impl<T: Foo + Default> Default for FooHolder<T> {
    fn default() -> Self {
        FooHolder { myfoo: T::default() }
    }
}

Full Playground

Otherwise, in case you just want to avoid making new type parameters and exposing constructor methods, there is usually no problem with moving T into the holder. In fact, many APIs in the standard library and popular crates follow this approach.

impl<T> FooHolder<T> {
    fn new(foo: T) -> Self {
        FooHolder { myfoo: foo }
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM