【问题标题】:Why can't I add a blanket impl on a trait with a type parameter?为什么我不能在带有类型参数的特征上添加一揽子 impl?
【发布时间】:2017-03-05 21:03:18
【问题描述】:

考虑这两个特征:

pub trait Foo {
    fn new(arg: u32) -> Self;
}

pub trait Bar<P>: Foo {
    fn with_parameter(arg: u32, parameter: P) -> Self;
}

我想添加一揽子 impl:

impl<T: Bar<P>, P: Default> Foo for T {
    fn new(arg: u32) -> Self {
        Self::with_parameter(arg, P::default())
    }
}

但我得到编译器错误:

error[E0207]: the type parameter `P` is not constrained by the impl trait, self type, or predicates
 --> src/lib.rs:9:17
  |
9 | impl<T: Bar<P>, P: Default> Foo for T {
  |                 ^ unconstrained type parameter

我认为我收到此错误是因为我违反了 trait coherence 规则,但我不明白这会破坏什么规则。为什么不允许这种模式?而且,更重要的是,我可以在不出错的情况下实现我想要的吗?

【问题讨论】:

    标签: generics rust traits type-parameter


    【解决方案1】:

    问题是单个类型可以为P 的多个值实现Bar&lt;P&gt;。如果您有一个实现Bar&lt;i32&gt;Bar&lt;String&gt; 的结构Baz,那么Foo::new 应该为P 使用哪种类型?

    唯一的解决方案是确保单个类型不能多次实现Bar(如果这不是您想要的,那么您的设计就有缺陷!)。为此,我们必须将P 类型参数替换为关联类型。

    pub trait Bar: Foo {
        type Parameter;
    
        fn with_parameter(arg: u32, parameter: Self::Parameter) -> Self;
    }
    
    impl<T> Foo for T
    where
        T: Bar,
        T::Parameter: Default,
    {
        fn new(arg: u32) -> Self {
            Self::with_parameter(arg, T::Parameter::default())
        }
    }
    

    Bar 的实现如下所示:

    struct Baz;
    
    impl Bar for Baz {
        type Parameter = i32;
    
        fn with_parameter(arg: u32, parameter: Self::Parameter) -> Self {
            unimplemented!()
        }
    }
    

    另见:

    【讨论】:

      【解决方案2】:

      我已经分解并扩展了Francis's explanation 代码无法编译的原因。我可能不是街区里最聪明的孩子,但我花了很长时间才理解他简洁的推理。

      让我们创建Baz,它在两个变体中实现Bari32String

      struct Baz;
      
      impl Bar<i32> for Baz { /* ... */ }
      
      impl Bar<String> for Baz { /* ... */ }
      

      一揽子impl生效后的类型依赖图:

                 -> trait Bar<i32>    -> trait Foo (with i32 baked-in)
      struct Baz
                 -> trait Bar<String> -> trait Foo (with String baked-in)
      

      我们最终得到了 Foo 的 2 种不同实现:带有烘焙的 i32 和烘焙的 String。 当我们写&lt;Baz as Foo&gt;::new()时,编译器无法分辨出我们指的是哪个版本的Foo;它们是无法区分的。

      经验法则是,只有当 trait A 对 trait B 的所有通用参数都是通用的时,trait A 才能对 trait B 进行全面实现。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2019-08-15
        • 1970-01-01
        • 2015-12-02
        • 1970-01-01
        • 1970-01-01
        • 2016-05-14
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多