具有关联类型的工厂方法
Posted
技术标签:
【中文标题】具有关联类型的工厂方法【英文标题】:Factory method with associated type 【发布时间】:2020-05-12 17:11:26 【问题描述】:我正在尝试实现一个工厂方法,该方法返回具有关联类型的Service
。我让它在没有关联类型的情况下工作,但是一旦我添加它,无论我如何按摩它,我都无法让它编译..
这是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>;
所以想法是sanitize
函数返回Query
的一个实例,然后可以将其传递给run
函数。
工厂看起来像这样(不编译):
fn factory<Q: QType>(name: &str) -> Box<dyn Service<Query = Q>>
match name
"amazon" => Box::new(amzn::Amazon ),
other => panic!("Invalid service ", other),
现在我这里只有一个服务,我可以在签名中指定类型参数——这将使它编译——但我想要一个通用工厂方法并添加更多服务。
这是Amazon
服务的实现:
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>
编译器说:
错误[E0271]:类型不匹配解析`::Query == Q` --> src/main.rs:9:21 | 9 | “亚马逊” => Box::new(amzn::Amazon ), | ^^^^^^^^^^^^^^^^^^^^^^^^^ 预期类型参数,找到 struct `amzn::Product` | =注意:预期类型`Q` 找到类型`amzn::Product` = help: 类型参数必须被约束以匹配其他类型 = 注意:有关更多信息,请访问 https://doc.rust-lang.org/book/ch10-02-traits.html#traits-as-parameters = 注意:强制转换为对象类型 `dyn Service` 是必需的基于此错误消息,我不确定如何指定类型参数。我已经尝试提取Amazon
的创建并为其提供显式类型参数,但这只会留下不同的错误。此外,在本书链接的第 10.02 章之后,并没有对关联类型的案例进行任何解释。最后,我也尝试了RFC-1598: Generic Associated Types的路由,但我无法编译,也不确定我是否真的需要它。
另外请注意,我添加了 Box
包装器和 QType
限制,基于其他关于类似问题的答案,但我可能完全走错了路..
非常感谢任何帮助。
【问题讨论】:
【参考方案1】:此签名无法实现:
fn factory<Q: QType>(name: &str) -> Box<dyn Service<Query = Q>>
关联类型始终由实现类型唯一确定。 IE。 Service
的每个实现只选择一个关联类型 Query
。
这与factory
不一致,后者让调用者决定关联的类型应该是什么。应该清楚地看到,如果您使用不是 Product
的 Q
调用 factory
,那么 match
表达式中的代码将不再进行类型检查。
您可以通过修复Query
的选择来完成这项工作:
fn factory(name: &str) -> Box<dyn Service<Query = Product>>
match name
"amazon" => Box::new(amzn::Amazon ),
other => panic!("Invalid service ", other),
如果您希望调用者选择类型,那么您需要找到一种方法,以便函数体适用于Q
的任何选择。例如,您可以将构造与 QType
特征关联:
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")
并为您的类型实现:
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,
【讨论】:
谢谢,这真的帮助我理解了如何设置Q: QType
约束并没有真正的意义。
非常感谢!是否可以在不具体说明调用站点的类型参数的情况下使用它?我试过let service = factory("amazon"); service.run(&service.sanitize("usb charger").unwrap()).unwrap();
,但编译器希望指定Q
。如果我将它设置为Product
,它就可以工作,但我更愿意保持通用【参考方案2】:
我认为工厂方法可以使用动态调度实现,但没有关联类型。
我在rust playground 中使用您的示例实现了该模式。
具有动态调度的 Service 的特征现在如下所示:
trait Service
fn sanitize(&self, query: &str) -> Result<Box<dyn QType>, String>;
fn run(&self, query: &dyn QType) -> Result<(), String>;
注意方法参数使用 trait 对象而不是具体类型。
由于我们实际上需要恢复具体类型,因此我们将使用 Any trait 和 AsAny 策略,以便能够从 trait 对象向下转换为具体类型。
use std::any::Any;
trait QType: Any + AsAny
trait AsAny
fn as_any(&self) -> &dyn Any;
impl<Q: QType> AsAny for Q
fn as_any(&self) -> &dyn Any
self
fn downcast_ref_qtype<Q: QType> (qtype: &dyn QType) -> Result<&Q, String>
qtype.as_any().downcast_ref::<Q>().ok_or_else(|| "QType not supported".to_owned())
在具体实现中,我们可以使用这个 downcast_ref_qtype 方法来取回我们的具体类型:
impl Service for Amazon
fn sanitize(&self, query: &str) -> Result<Box<dyn QType>, String>
// sanitize implementation
// ...
let product = Product
name: query.to_owned()
;
Ok(Box::new(product))
fn run(&self, query: &dyn QType) -> Result<(), String>
let product: &Product = downcast_ref_qtype(query)?;
// run implementation
// ...
Ok(())
由于 Service trait 不再有关联类型,现在我们可以实现我们的工厂方法:
fn factory(name: &str) -> Box<dyn Service>
match name
"amazon" => Box::new(amzn::Amazon ),
other => panic!("Invalid service ", other),
fn main()
let service = factory("amazon");
service.run(service.sanitize("usb charger").unwrap().as_ref()).unwrap();
总而言之,我们将关联类型隐藏在 run 方法的具体实现中,并在调用 downcast 函数时定义它。
编辑:如果你仍然想要你的关联类型,我找到了一种添加另一个层的方法,这反过来又可以让你在需要时仍然使用你的 trait 和静态调度。
我们添加另一个具有关联类型的特征。
trait ServiceImpl
type Q: QType;
fn sanitize(&self, query: &str) -> Result<Self::Q, String>;
fn run(&self, query: &Self::Q) -> Result<(), String>;
我们添加了一个全面的实现,它为任何 ServiceImpl 实现者实现 Service trait 并负责向下转换。
impl <SI: ServiceImpl> Service for SI
fn sanitize(&self, query: &str) -> Result<Box<dyn QType>, String>
Ok(Box::new(ServiceImpl::sanitize(self, query)?))
fn run(&self, query: &dyn QType) -> Result<(), String>
let query = downcast_ref_qtype::<SI::Q>(query)?;
ServiceImpl::run(self, query)
现在我们只需要实现 ServiceImpl 并且结构体既可以用作 trait 对象也可以用于静态调度
impl ServiceImpl for Amazon
type Q = Product;
fn sanitize(&self, query: &str) -> Result<Product, String>
// sanitize implementation
// ...
let product = Product
name: query.to_owned()
;
Ok(product)
fn run(&self, query: &Product) -> Result<(), String>
let product: &Product = downcast_ref_qtype(query)?;
// run implementation
// ...
Ok(())
您可以在此处查看完整示例:rust playground
如果有帮助请告诉我:)
最好的问候, CHBS
【讨论】:
以上是关于具有关联类型的工厂方法的主要内容,如果未能解决你的问题,请参考以下文章