在使用参数的生命周期作为特质参数的限定条件时,出现了“预期关联类型,发现`u32`”的错误。

15

我尝试编译这段代码 (Playground):

trait Family<'a> {
    type Out;
}

struct U32Family;
impl<'a> Family<'a> for U32Family {
    type Out = u32;
}


trait Iterator {
    type Item;
    fn next<'s>(&'s mut self) -> <Self::Item as Family<'s>>::Out
    where
        Self::Item: Family<'s>;
}


struct Foo;
impl Iterator for Foo {
    type Item = U32Family;

    fn next<'s>(&'s mut self) -> <Self::Item as Family<'s>>::Out
    where
        Self::Item: Family<'s>,
    {
        0u32  // <-- in real code, this is somehow calculated
    }
}

但遗憾的是,它导致了这个错误:

error[E0308]: mismatched types
  --> src/main.rs:28:9
   |
24 |     fn next<'s>(&'s mut self) -> <Self::Item as Family<'s>>::Out
   |                                  ------------------------------- expected `<U32Family as Family<'s>>::Out` because of return type
...
28 |         0u32
   |         ^^^^ expected associated type, found u32
   |
   = note: expected type `<U32Family as Family<'s>>::Out`
              found type `u32`

我真的不明白为什么。显然,在这段代码中,<U32Family as Family<'s>>::Out 就是 u32。但 Rust 似乎认为它并不总是相同的。为什么?我该如何让它编译通过?

一些注释:

  • 有很多类似的情况会出现类似的错误,但我认为这与我之前看到的所有情况都不同。
  • 我不能使用 type Out: for<'a> Family<'a>;。所以这不是适合我的解决方法。
  • 如果我删除 Family 的生命周期参数,一切都正常。
  • 如果我在函数签名中将 Family<'s> 替换为 Family<'static>,一切都正常。

编辑: 我可以通过添加以下内容来解决此问题:

impl U32Family {
    fn from<'a>(v: u32) -> <Self as Family<'a>>::Out {
        v
    }
}

然后我就可以在next()的主体中写Self::Item::from(0u32)。(Playground

我认为大家都清楚了,为什么next()中的错误消失了:因为U32Family::from总是以为参数。 硬编码。 永远不会改变。 关于这个解决方法的更大问题是:为什么from()方法能够编译通过? 所以,在from()中,编译器以某种方式知道<Self as Family<'a>> :: Out始终是u32,但如果我尝试在next()中做同样的事情,那么编译器就无法理解<Self :: Item as Family<'s>> :: Out u32。 现在我更加困惑了。

EDIT2:首先,我怀疑专业化是问题所在。 例如,您可以编写:

impl Family<'static> for U32Family {
    type Out = char;
}

当然,编译器会正确地假设对于任何 'su32 并不总是与 <Self::Item as Family<'s>>::Out 相同。 但是,我认为这不是问题的所在。

首先,可以进行特化的impl 需要用关键字 default 进行标记。我没有这样做,因此我应该能够假设关联类型实际上是 u32RFC 讨论了非常相似的内容)。但另外,基于生命周期的特化不被允许

所以现在我倾向于认为这是编译器错误。但我很想得到另一个答案!


你可以将trait分为两部分来编译:trait FamilyBase { type Out; } 用于定义类型,以及 trait Family<'a> : FamilyBase { } 用于实际操作。可能有点hacky... - rodrigo
@rodrigo 谢谢你的想法!那种方法的问题在于实现者需要能够在 Out 类型中使用生命周期参数,比如 impl<'a> Family<'a> for Bar { type Out = &'a bool; }。这在你提出的两个 trait 的想法中是不可能的 :/ - Lukas Kalbertodt
我明白了。那么请在trait Family中添加此函数:fn zero() -> Self::Out;,然后在Iterator::next()中调用<Self::Item as Family<'s>>::zero() - rodrigo
如果你将特质约束从 next 函数转移到整个 Iterator 特质(即 type Item: Family<'a>),则可以使其编译。但这确实需要在 Iterator 中添加一个生命周期。Playground - Kwarrtz
@计算器 同样的,很遗憾我不能使用HRTBs。但是感谢您的想法!正如您所看到的,我再次编辑了问题,并认为这是编译器的错误 :/ - Lukas Kalbertodt
显示剩余5条评论
2个回答

2
我认为问题在于,对于所有的 's,>::Out 都是 u32,这只是一种“巧合”。编译器可以证明你想要的任何 's,但它甚至无法表达这个概念对于所有 's 都是真实的。
你找到的解决方法是正确的:向 U32Family 添加一个方法,将 u32 转换为 >::Out。该方法的主体完全在 'a 的范围内,因此编译器可以证明转换对于该 'a 是类型正确的,因此该方法是类型正确的。然后,在调用站点,你告诉编译器使用它对该方法的知识。

0
struct U32Family;
...
impl Iterator for Foo {
type Item = U32Family;

因此,next() 必须返回 Option<U32Family>,其唯一可能的值是 NoneSome(U32Family{})

您可能想要 Item = <U32Family as Family<'static>::Out 来解决这个问题,但会创建一些生命周期问题。(Item 需要一个生命周期,因为 Family 有一个生命周期,但您只接受 next() 上的生命周期)


我的问题中的Iterator特质是自己制作的,而不是来自std的官方特质。在我的特质中,我不返回Option<Self::Item>,而是关联的Out类型。无论如何,我不能仅仅出于几个原因而使用'static。但还是谢谢你的回答 :)!如果您感兴趣,我的问题是在写这篇博客文章时遇到的。它应该解释了我的奇怪要求。 - Lukas Kalbertodt

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接