【问题标题】: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 限制,基于其他关于类似问题的答案,但我可能完全走错了路..

非常感谢任何帮助。

【问题讨论】:

    标签: rust


    【解决方案1】:

    此签名无法实现:

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

    关联类型始终由实现类型唯一确定。 IE。 Service 的每个实现只选择一个关联类型 Query

    这与factory 不一致,后者让调用者决定关联的类型应该是什么。应该清楚地看到,如果您使用不是 ProductQ 调用 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(&amp;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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-04-30
      • 1970-01-01
      • 2010-11-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多