Mam cechę z parametrem typu ogólnego. Chcę umieścić różne przedmioty, które wdrażają tę cechę w kolekcji. Obiekt ma różne parametry typu.

Kiedy to robię, kompilator mówi mi, że muszę określić parametr typu ogólnego. Właściwie nie potrzebuję tej ogólnej informacji o moim przypadku, więc jakiś rodzaj wieloznaczny działał dla mnie. Pozwól mi pokazać kod, ponieważ pokazuje mój zamiar lepiej:

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

Błąd:

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

Rozumiem, dlaczego kompilator daje mi ten błąd: Położyłem foo pierwszy i ma parametr typu I64. Po tym kompilatorze spodziewa się tylko I64 jako parametr typu. Ale czy istnieje sposób na obejście?

1
Artem Malinko 22 październik 2020, 14:05

1 odpowiedź

Najlepsza odpowiedź

Myślę, że nie możesz sprawić, że to działa dokładnie tak.

Twoje opcje osiągnięcia podobnego wyniku są albo mieć elementy zaimplementować kolejną cechę niezwykłą, którą następnie dodasz do swojego Vec, jeśli nie wiesz przed czasem, które typy dla T są ostatecznie możliwe, tj. Cecha jest częścią twojego publicznego interfejsu API, a inne skrzynie oczekuje się, że wdroży je na własne typy 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
        ()
    });
}

Albo jeśli znasz wszystkie możliwe typy T przed czasem można zmienić T w cechy do enum, który zawiera wszystkie typy, które chcesz obsługiwać tutaj:

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
        ()
    });
}
3
FlyingFoX 22 październik 2020, 12:07