1

I'm trying to implement a trait that contains a generic function. However, I cannot instantiate a struct inside the implemented function.

trait Extractor {
    fn parse(&self) -> String;
}

struct BooksExtractor {}

impl Extractor for BooksExtractor {
    fn parse(&self) -> String {
        "test".to_owned()
    }
}

struct Pass<E: Extractor> {
    pub extractor: Option<E>,
    pub name: String,
    pub next_pass: Option<Box<Pass<E>>>,
}

trait Robot: Sized {
    fn get_pass<E: Extractor>(&self) -> Pass<E>;
}

struct GutenbergRobot {}

impl Robot for GutenbergRobot {
    fn get_pass<E: Extractor + ?Sized>(&self) -> Pass<E>
        where E: Extractor + Sized {
        Pass {
            extractor: Some(BooksExtractor {}),
            name: "test".to_owned(),
            next_pass: None
        }
    }
}

The compiler complains about the parameter expected to be a type for the extractor field when trying to instantiate a Pass struct inside the implemented function:

error[E0308]: mismatched types
  --> src\main.rs:33:29
   |
33 |             extractor: Some(BooksExtractor {}),
   |                             ^^^^^^^^^^^^^^^^^ expected type parameter, found struct `BooksExtractor`
   |
   = note: expected type `E`
              found type `BooksExtractor`
Boiethios
  • 38,438
  • 19
  • 134
  • 183
JoeHigashi
  • 67
  • 1
  • 5

2 Answers2

2

Your Robot::get_pass function should not be generic. Instead you should use an associated type:

trait Robot: Sized {
    type Extractor: Extractor;
    fn get_pass(&self) -> Pass<Self::Extractor>;
}

impl Robot for GutenbergRobot {
    type Extractor = BooksExtractor;
    fn get_pass(&self) -> Pass<BooksExtractor> {
        Pass {
            extractor: Some(BooksExtractor {}),
            name: "test".to_owned(),
            next_pass: None
        }
    }
}

In short, you should use a generic when the caller can choose the type returned by the function, and an associated type when the implementer chooses the type.

See also this question.

edwardw
  • 12,652
  • 3
  • 40
  • 51
Jmb
  • 18,893
  • 2
  • 28
  • 55
1

Your function signature for get_pass specifies that it will construct a Pass<E> type. That means it should work everywhere where Pass<E: Extractor + Sized> is valid.

Imagine you have the following struct in addition to your code:

struct OtherBooksExtractor {}
impl Extractor for OtherBooksExtractor {
    fn parse(&self) -> String {
        "foobar".to_owned()
    }
}

Then you will expect that the following to work, based on just the types:

fn bla() -> Pass<OtherBookExtractor> {
   GutenbergRobot.get_pass()
}
assert_eq!(bla.extractor.parse(), "foobar")

However, you are constructing something more specific than the generic Pass<E> in your get_pass: a Pass<BooksExtractor>! If that would allow, that means that the code I wrote would not work as I might have expected.

Thom Wiggers
  • 6,938
  • 1
  • 39
  • 65