我如何构造实现特征的潜在许多结构中的任何一个?

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

我创建了一个小的工作示例来说明我在下面的 Rust 中遇到的问题:

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();
}

编译失败:

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>`

我希望能够 return 从 FooHolder::new() 方法实现 TraitFoo 的潜在许多结构中的任何一个。在这种情况下,我希望它期望任何 T:TraitFoo 作为 return 类型而不仅仅是 StructBar

我已经尝试了几种方法,但是像将 new() 移动到特征中这样的东西对我没有帮助,因为实现 TraitBar 的新结构可能会在 new() 中采用不同的参数。

您似乎几乎走在了正确的轨道上。让我们介绍一下要点:

函数 fn new() -> FooHolder<T> 的实现无法选择类型 T。这是由调用它的上下文选择的。所以我们不能强制执行,或者总是假设 T = StructBar.

通常,您会做以下两件事之一:在 T 实现的特征中为 T 提供一个构造函数接口。

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

即使您可以这样做,编译器如何知道 FooHolder::new() 中期望的参数?可变数量的参数超出您的范围(参见 ),因此告诉编译器根据 T 接受不同数量的参数是不现实的。但是,我们可以通过定义构造参数的关联类型来模拟它。在下面的代码中,我冒昧地使标识符更加惯用(StructTrait 作为前缀只会引入噪音)。

trait Foo {
    type Params; // new type parameter

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

    fn foo(&self) -> i32;
}

我们的 Bar 会这样定义:

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 }
    }
}

我们的 FooHolder 现在可以构造一个 T:

类型的值
struct FooHolder<T: Foo> {
    myfoo: T,
}

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

使用FooHolder:

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

当不需要参数来构造T时,我们可以依赖Default特征:

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

Full Playground

否则,如果你只是想避免创建新的类型参数和暴露构造函数方法,将 T 移入 holder 通常没有问题。事实上,标准库和流行的 crate 中的许多 API 都遵循这种方法。

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