【问题标题】:Implement Pseudo Naked Constraints实施伪裸约束
【发布时间】:2020-12-17 06:55:04
【问题描述】:

我正在清除 Rust 中的依赖注入 (DI)。我有一些工作,但我正在努力使其更好。据我了解,Rust 确实支持带有类型参数的裸约束(就像其他一些语言一样)。我正在尝试确定是否有可能以不安全的方式伪造裸约束(假设这是必要的)。对于不正确的映射,我完全可以接受史诗般的失败。我对 Rust 的理解很简单,我需要说服它 T 确实实现了 U 并且可以隐式或显式地转换为它。 cast,我的意思是一个具体但通用的类型可以转换为一个 trait 对象,因为它实现了它(我意识到大多数人要求另一个方向,我理解那里的复杂性)。

包括我所拥有的一切会有点多;但是,这里有一些上下文。考虑一下我们可以将工厂函数归结为(这可行):

Rc<dyn Fn(&ServiceProvider) -> Rc<dyn Any>>

目前,我有一个类似于(删节)的构建器:

pub struct ServiceDescriptorBuilder<TSvc: Any + ?Sized, TImpl> {
    _phantom_svc: PhantomData<TSvc>,
    _phantom_impl: PhantomData<TImpl>,
}

impl<TSvc: Any + ?Sized, TImpl> ServiceDescriptorBuilder<TSvc, TImpl> {
    pub fn from<F>(self, factory: F) -> ServiceDescriptor
    where
        F: Fn(&ServiceProvider) -> Rc<TSvc> + 'static, {
        // convert to factory
        let factory = Rc::new(move |sp| Rc::new(factory(sp)));
    }
}

这完全适用于:

let descriptor = ServiceDescriptorBuilder::<dyn Foo, FooImpl>::new(/* unimportant */)
                  .from(|sp| Rc::new(FooImpl::default()))

我的目标是在工厂关闭中删除额外的Rc 声明。 所有实例都包含在Rc 中。瞬态生命周期总是得到一个新的Rc,计数为 1,而单例有一个克隆的Rc。我想要做的是(例如减去Rc):

let descriptor = ServiceDescriptorBuilder::<dyn Foo, FooImpl>::new(/* unimportant */)
                  .from(|sp| FooImpl::default())

我可以得到第二个更简单的变体来编译,但它在运行时会失败——原因很明显。我需要说服 Rust 使用拥有值为FooImplRc&lt;dyn Foo&gt;。这对于泛型来说似乎是不可能的。关联类型也许是可能的,但这只是一种选择,如果它仍然产生简洁的语法。真正让我无法理解的是编译器本身可以做到的magic。理解Rc&lt;FooImpl&gt;是协变的,可以满足Rc&lt;dyn Foo&gt;,似乎没有问题。

我有一些工作,所以这并不是真正的阻塞,但让我很恼火的是,这不能以更简单的形式实现。也许这只是语言的当前限制。其他一些语言将允许将其表示为where TImpl: TSvc。我不会假装理解为什么这是困难或尚不支持,但其他语言已经弄清楚了。我知道已经有一个限制,您不能将类型限制为特征。也许这是使这更简洁所需的唯一功能。

我满足于等待,但似乎有一些不安全的方法可以让Rc 拥有FooImpl 的实例,但表示为Rc&lt;dyn Foo&gt;。除非我有严重的误解,否则这就是编译器最终产生的结果。我不清楚如何强制或复制它。

我不喜欢使用宏来解决这个问题,但我认为这是一个可以接受的解决方案;特别是,如果它可以作为实现细节隐藏起来(来自调用者/开发者)。虽然我当前的解决方案确实有效并且更加冗长,但它确实提供了某种程度的编译器检查,因为您不能只返回 anything。删除此检查的更简洁的语法(使用transmute 或其他东西)可能不值得权衡。

建议?

【问题讨论】:

    标签: rust


    【解决方案1】:

    如果我的总结正确,您基本上想从具体类型 FooImpl 变为特征对象 dyn Foo,但在通用设置中(在本例中通过 Rc)。

    use std::rc::Rc;
    
    fn to_dyn<DynTrait, Impl>(i: Rc<Impl>) -> Rc<DynTrait> {
        // ... some implementation
    }
    

    在稳定的 Rust 中没有现有的约束可以做到这一点。您必须为要使用的每种类型手动实现类似 ToDyn 的特征;可能不理想。我不知道如何做到这一点,否则甚至不安全。


    但是,这可以通过 Unsize&lt;T&gt; trait 在夜间完成。

    #![feature(unsize)]
    
    use std::rc::Rc;
    use std::marker::Unsize;
    
    fn to_dyn<DynTrait: ?Sized, Impl>(i: Rc<Impl>) -> Rc<DynTrait>
    where 
        Impl: Unsize<DynTrait> // `Impl` can be "unsized" into `DynTrait`
    {
        i
    }
    

    这是RFC 982: DST Coercion 的一部分,它本身是更大的RFC 401: Type Conversions 的一部分。

    【讨论】:

    • 你明白了。我肯定会避免 Nightly ,因为我有一些工作。似乎这只是当前的限制。我也忘了提到 Self 的情况,这实际上意味着有两个可能的约束。 TImpl 是一些实现TSvc Tmpl struct TSvc 的结构。尽管 Rust 中没有继承,但仍然存在通过结构注入具体类型的用例。
    猜你喜欢
    • 2016-12-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-12-11
    • 2021-06-30
    • 2023-03-18
    • 1970-01-01
    相关资源
    最近更新 更多