6
votes

I'm trying to implement a factory method that returns a Service with an associated type. I got it to work without the associated type, but once I add that, I can't get it to compile regardless of how I massage it..

This is the Service:

trait QType {}

trait Service {
    type Query: QType;

    fn sanitize(&self, query: &str) -> Result<Self::Query, String>;

    fn run(&self, query: &Self::Query) -> Result<(), String>;
}

So the idea is that the sanitize function returns an instance of the Query, which can then be passed to the run function.

The factory looks like this (doesn't compile):

fn factory<Q: QType>(name: &str) -> Box<dyn Service<Query = Q>> {
    match name {
        "amazon" => Box::new(amzn::Amazon {}),
        other => panic!("Invalid service {}", other),
    }
}

Now I only have one service here and I could be specific in the type Parameters in the signature -- which would make it compile -- but I want to have a generic factory method and add more services.

Here's the implementation of the Amazon service:

mod amzn {
    use super::*;

    pub struct Amazon {}

    pub struct Product {
        name: String,
    }

    impl QType for Product {}

    impl Service for Amazon {
        type Query = Product;
        fn sanitize(&self, query: &str) -> Result<Product, String> {}
        fn run(&self, query: &Product) -> Result<(), String> {}
    }
}

The compiler says:

error[E0271]: type mismatch resolving `::Query == Q`
 --> src/main.rs:9:21
  |
9 |         "amazon" => Box::new(amzn::Amazon {}),
  |                     ^^^^^^^^^^^^^^^^^^^^^^^^^ expected type parameter, found struct `amzn::Product`
  |
  = note: expected type `Q`
             found type `amzn::Product`
  = help: type parameters must be constrained to match other types
  = note: for more information, visit https://doc.rust-lang.org/book/ch10-02-traits.html#traits-as-parameters
  = note: required for the cast to the object type `dyn Service`

Based on this error message, I 'm not sure how to specify the type parameter. I have tried extracting the creation of Amazon and giving it explicit type parameters, but that just leaves to different errors. Also, following the linked chapter 10.02 in the book doesn't give any explanations on the case with associated types. Lastly, I also tried the route of RFC-1598: Generic Associated Types, but I could neither get it to compile nor am I sure whether I really need that.

Also please note that I added the Box wrapper and QType restriction based on other answers here on SO around similar issues, but I could very be completely on the wrong path here..

Any help is much appreciated.

1

1 Answers

8
votes

This signature is not possible to implement:

fn factory<Q: QType>(name: &str) -> Box<dyn Service<Query = Q>>

An associated type is always uniquely determined by the implementing type. I.e. every implementation of Service chooses just one associated type Query.

This is at odds with factory, which is letting the caller decide what the associated type should be. It should be clear to see that if you call factory with Q that is not Product then the code inside the match expression no longer type checks.

You can make this work by fixing the choice of Query:

fn factory(name: &str) -> Box<dyn Service<Query = Product>> {
    match name {
        "amazon" => Box::new(amzn::Amazon {}),
        other => panic!("Invalid service {}", other),
    }
}

If you want the caller to choose the type, then you need to find a way so that the body of the function will work for any choice of Q. For example, you can associate the construction with the QType trait:

trait QType {
    fn create_service(name: &str) -> Option<Box<dyn Service<Query = Self>>>;
}

fn factory<Q: QType>(name: &str) -> Box<dyn Service<Query = Q>> {
    Q::create_service(name).expect("Invalid service")
}

And implement for your type:

impl QType for Product {
    fn create_service(name: &str) -> Option<Box<dyn Service<Query = Self>>> {
        match name {
            "amazon" => Some(Box::new(amzn::Amazon {})),
            other => None,
        }
    }
}