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.
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.
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.
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).
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() }
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,
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With