Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Factory method with associated type

Tags:

rust

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.

like image 454
rethab Avatar asked Jan 27 '20 10:01

rethab


People also ask

What is associated type in Swift?

An associated type gives a placeholder name to a type that's used as part of the protocol. The actual type to use for that associated type isn't specified until the protocol is adopted. Associated types are specified with the associatedtype keyword.

Can only be used as a generic constraint because it has self or associated type requirements?

Protocol 'SomeProtocol' can only be used as a generic constraint because it has Self or associated type requirements. Code that uses a protocol that relies on associated types pays the price. Such code must be written using generic types. Generic types are also placeholders.

What is a factory in Swift?

Factory method is a creational design pattern which solves the problem of creating product objects without specifying their concrete classes. The Factory Method defines a method, which should be used for creating objects instead of using a direct constructor call ( new operator).

What is a swift protocol?

In Swift, a protocol defines a blueprint of methods or properties that can then be adopted by classes (or any other types). We use the protocol keyword to define a protocol. For example, protocol Greet { // blueprint of a property var name: String { get } // blueprint of a method func message() }


1 Answers

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,
        }
    }
}
like image 155
Peter Hall Avatar answered Sep 28 '22 07:09

Peter Hall