是否有可能拥有动态通用特征的集合?

Art*_*nko 1 rust

我有一个带有泛型类型参数的特征。我想将实现此特征的不同对象放入一个集合中。对象有不同的类型参数。

当我这样做时,编译器告诉我需要指定泛型类型参数。实际上,我的情况不需要这种通用类型信息,因此某种通配符适合我。让我展示代码,因为它更好地表明了我的意图:

trait Test<T> {
    fn test(&self) -> T;
}
struct Foo;
struct Bar;
impl Test<i64> for Foo {
    fn test(&self) -> i64 {
        println!("foo");
        42
    }
}
impl Test<String> for Bar {
    fn test(&self) -> String {
        println!("bar");
        "".to_string()
    }
}

fn main() {
    // I'm not going to invoke test method which uses generic type parameter.
    // So some kind of wildcard would work for me.
    // But underscore is not wildcard and this does not compile.
    let xs: Vec<Box<dyn Test<_>>> = vec![Box::new(Foo), Box::new(Bar)];
    xs.iter().map(|x| {
        // do some stuff, but not invoke Test::test() method, so I don't need type information
        ()
    });
}
Run Code Online (Sandbox Code Playgroud)

错误是:

error[E0277]: the trait bound `Bar: Test<i64>` is not satisfied
  --> src/main.rs:24:57
   |
24 |     let xs: Vec<Box<dyn Test<_>>> = vec![Box::new(Foo), Box::new(Bar)];
   |                                                         ^^^^^^^^^^^^^ the trait `Test<i64>` is not implemented for `Bar`
   |
   = help: the following implementations were found:
             <Bar as Test<std::string::String>>
   = note: required for the cast to the object type `dyn Test<i64>`
Run Code Online (Sandbox Code Playgroud)

我明白为什么编译器给我这个错误:我把 Foo 放在第一位,它有 i64 作为类型参数。之后,编译器只期望 i64 作为类型参数。但有没有办法解决这个问题呢?

Fly*_*FoX 5

我认为你不能让它完全像这样工作。

要实现类似的结果,您可以选择让您的元素实现另一个非通用特征,然后Vec如果您事先不知道最终T可能的类型,则将其添加到您的元素中,即该特征是您的公共 API 和其他板条箱的一部分预计会为自己的类型实现它T

trait NonGenericTest {}
trait Test2<T> : NonGenericTest {
    fn test(&self) -> T;
}

impl NonGenericTest for Foo{}
impl NonGenericTest for Bar{}

impl Test2<i64> for Foo {
    fn test(&self) -> i64 {
        println!("foo");
        42
    }
}
impl Test2<String> for Bar {
    fn test(&self) -> String {
        println!("bar");
        "".to_string()
    }
}
fn main()  {
    let xs: Vec<Box<dyn NonGenericTest>> = vec![Box::new(Foo), Box::new(Bar)];
    xs.iter().map(|x| {
        // do some stuff, but not invoke Test::test() method, so I don't need type information
        ()
    });
}
Run Code Online (Sandbox Code Playgroud)

T或者,如果您提前知道所有可能的类型,您可以将T特征中的 更改为包含您想要支持的所有类型的枚举:

enum TestResult {
  ResultI64(i64),
  ResultString(String),
}

trait Test {
    fn test(&self) -> TestResult;
}
struct Foo;
struct Bar;
impl Test for Foo {
    fn test(&self) -> TestResult {
        println!("foo");
        TestResult::ResultI64(42)
    }
}
impl Test for Bar {
    fn test(&self) -> TestResult {
        println!("bar");
        TestResult::ResultString("".to_string())
    }
}
fn main(){
    let xs: Vec<Box<dyn Test>> = vec![Box::new(Foo), Box::new(Bar)];
    xs.iter().map(|x| {
        // do some stuff, but not invoke Test::test() method, so I don't need type information
        ()
    });
}
Run Code Online (Sandbox Code Playgroud)

  • 还有第三个选项,即创建子特征,例如在此[playground](https://play.rust-lang.org/?version=stable&amp;mode=debug&amp;edition=2018&amp;gist=a17916484920c5044a1b7d5308d21c07)中。这不是最漂亮的代码,因为到处都是“PhantomData”,但它可以工作。 (2认同)