终身省略是不是适用于 trait impls 中的方法?

Posted

技术标签:

【中文标题】终身省略是不是适用于 trait impls 中的方法?【英文标题】:Does lifetime elision work for methods in trait impls?终身省略是否适用于 trait impls 中的方法? 【发布时间】:2016-02-24 18:25:26 【问题描述】:

关于这个问题,我正在寻找在该领域有更多知识的人的反馈。我绝不是专家。所以我不妨提前问我的问题:我的推理在这里正确吗?

问题

基于 SO 上的answer to a question,我很困惑地看到在 trait 方法的实现中省略了生命周期:

impl<'a, 'b, T> PartialEq<RefEquality<'b, T>> for RefEquality<'a, T> 
    fn eq(&self, other: &RefEquality<T>) -> bool 
        self.0 as *const T == other.0 as *const T
    

这里,在方法签名中,other 类型的生命周期 'b 被省略了。这有效并且是正确的。我希望它是 &amp;RefEquality&lt;'b, T&gt; 类型是正确的。毕竟,这里的'b 是必不可少的:生命周期必须不同于'a。如果不是,那就太严格了:该实现只适用于另一个与Self 具有相同生命周期的RefEquality&lt;T&gt;。所以这些显然是不同的语义。编译器如何推断正确的生命周期?

终身省略处理它

函数签名的生命周期可以省略,但不能在 impl 块上省略。在那里,必须完全指定类型,包括命名生命周期。

另一方面,在 eq() 方法上,我可以在 other 的类型注释中省略生命周期。事实上,编译器随后会为其插入一个与'a 明显不同的任意生命周期。这就是为什么它在保持相同语义的同时有效的原因:

impl<'a, 'b, T> PartialEq<RefEquality<'b, T>> for RefEquality<'a, T> 
    fn eq<'c>(&self, other: &RefEquality<'c, T>) -> bool 
        self.0 as *const T == other.0 as *const T
    

在这里,我为该方法引入了一个任意的生命周期'c,这与编译器在生命周期省略的情况下所做的基本相同。

在我的 trait impl 中命名一生 'b 只是说它必须与 'a 不同(我也没有以任何方式将它们联系起来)。从逻辑上讲,这是行不通的:

impl<'a, 'b, T> PartialEq<RefEquality<'b, T>> for RefEquality<'a, T> 
    fn eq(&self, other: &RefEquality<'a, T>) -> bool 
        self.0 as *const T == other.0 as *const T
    

我在 impl 中说过类型会有所不同(基于它们的生命周期),但现在实际的 eq() 实现说它们是相同的。这会导致预期的类型错误。

如果我希望生命周期相等怎么办?在这种情况下我仍然可以使用生命周期省略,还是编译器会插入任意生命周期并报告类型错误? 事实证明,推理在这里也能正常工作:

impl<'a, T> PartialEq<RefEquality<'a, T>> for RefEquality<'a, T> 
    fn eq(&self, other: &RefEquality<T>) -> bool 
        self.0 as *const T == other.0 as *const T
    

省略的生命周期将被推断为 'a,同时保持 RefEquality&lt;T&gt; 类型必须具有相同生命周期的期望语义。

【问题讨论】:

【参考方案1】:

我们看一下rustc的过程,判断一个提供的impl方法是否对应 到特征中声明的签名。

代码中的位置是 compare_impl_methodlibrustc_typeck/check/compare_method.rs,评论很好, 然而,对于那些不是编译器黑客的人来说,即使是 cmets 也很难使用。

我不是编译器开发者,所以以下是基于我的 rust 经验 和解释!

特征中的声明对应于特定的函数类型, 并且impl块中的定义被解析为自己的函数类型。

对于这个问题,我认为只有类型检查的结论是重要的:

impl 函数是 trait 函数的子类型吗?”

子类型 I

If S is a subtype of T,子类型关系常写成 S <: t s>

听起来很合理。我们希望impl 块定义一个函数 可以像在 trait 中声明的函数一样安全使用。

案例一

这是省略的生命周期案例,但已明确说明。 我已经用恐慌替换了所有方法体,以强调这一点 函数签名检查完全不受 函数体。

impl<'a, 'b, T> PartialEq<RefEquality<'b, T>> for RefEquality<'a, T> 
    fn eq<'c>(&self, other: &RefEquality<'c, T>) -> bool 
        panic!()
    

特征需要一个类型的函数:

fn(&RefEquality<'a, T>, &RefEquality<'b, T>)

你提供一个函数类型:

fn<'c>(&RefEquality<'a, T>, &RefEquality<'c, T>)

看起来提供的 impl 比要求的“更通用”。 使用'c == 'b,则函数类型相同。

它是预期类型的​​子类型,因为我们总是可以使用fn&lt;'c&gt; 版本 安全地就位。

案例 2

对于您的第二个示例,它没有编译:

impl<'a, 'b, T> PartialEq<RefEquality<'b, T>> for RefEquality<'a, T> 
    fn eq(&self, other: &RefEquality<'a, T>) -> bool 
        panic!()
    

您可以添加绑定'b: 'a('b 比'a),然后it's ok:

impl<'a, 'b: 'a, T> PartialEq<RefEquality<'b, T>> for RefEquality<'a, T> 
    fn eq(&self, other: &RefEquality<'a, T>) -> bool 
        panic!()
    

特征需要一个类型的函数:

fn(&RefEquality<'a, T>, &RefEquality<'b, T>)

你提供一个函数类型:

fn(&RefEquality<'a, T>, &RefEquality<'a, T>)

我认为如果 'b 比 'a 更长寿,它们是兼容的似乎是合乎逻辑的,但让我们冷静地看待它。

让我们去掉常数因子:

特征需要一个类型的函数:

fn(Ref<'b>)

你提供一个函数类型:

fn(Ref<'a>)

我们也有where 'b: 'a的信息。我们如何才能看到它们是兼容的?

子类型 II:变异的攻击

子类型化:使用X 代替Y 是否安全?

方差: 如果XY的子类型,那么Foo&lt;X&gt;Foo&lt;Y&gt;呢?

另请参阅Wikipedia、Rustonomicon 了解方差。

生命周期的子类型定义是:

'x &lt;: 'y 表示'x'y 长。

让我们练习子类型化和引用变化。

什么时候可以安全地使用&amp;'x i32 而不是&amp;'y i32

'x'y 寿命更长时,它就是 安全更换。 'x'y 活得更长意味着 &amp;'x i32&amp;'y i32 的子类型:

'x &lt;: 'y =&gt; &amp;'x i32 &lt;: &amp;'y i32

子类型关系沿相同方向传播, 这称为协方差&amp;'a i32'a 参数中是协变的

函数的方差行为是这样的:

X &lt;: Y =&gt; fn(Y) &lt;: fn(X)

函数的行为与其参数类型相反。这是逆变, 逻辑上“相反”,因为它是相反的方向。

计算

对于这个问题,我们假设Ref&lt;'a&gt; 表现为 如果它包含 &amp;'a 引用,并且它具有相同的方差 就像&amp;'a 本身一样。

我们得到了绑定where 'b: 'a,这意味着:

'b &lt;: 'a

对引用和参考使用协方差规则:

'b &lt;: 'a =&gt; Ref&lt;'b&gt; &lt;: Ref&lt;'a&gt;

对函数使用逆变规则**

Ref&lt;'b&gt; &lt;: Ref&lt;'a&gt; =&gt; fn(Ref&lt;'a&gt;) &lt;: fn(Ref&lt;'b&gt;)

这是 rustc 提出的问题,是 impl 函数 特征函数的子类型。是的!

** w.r.t.函数参数:

如果我希望生命周期相等怎么办?

如果您的目标只是为相等生命周期的情况定义PartialEq,那么是的,省略的生命周期情况很好。它在impl中提供了更通用的功能,但类型检查器确定它是兼容的。

您还可以更改 RefEquality 类型相对于生命周期参数的方差。

如果您想要 RefEquality&lt;'a, T&gt; 仅与子类型兼容 具有完全相同的生命周期,这称为不变性

您可以使用具有不变性的原语std::cell::Cell&lt;T&gt;Cell&lt;T&gt;T 参数中是不变的。

完成此操作的常用方法是PhantomData 成员:

struct RefEquality<'a, T: 'a> 
    ptr: &'a T,
    marker: PhantomData<Cell<&'a ()>>,

如果您想查看不变性的应用,请查看 横梁板条箱以及Scope&lt;'a&gt; being invariant in the 'a parameter 如何成为其特殊借用规则的基石 安全范围的线程。

【讨论】:

这...是一个了不起的答案。感谢您显然为描述这一点所做的所有工作! 不应该是 PhantomData&lt;Cell&lt;&amp;'a T&gt;&gt; 而不是 PhantomData&lt;Cell&lt;&amp;'a ()&gt;&gt; 以便对通用 T 有正确的删除语义吗? &amp;T 没有我知道的 dropck 含义,并且类型中已经有一个 &amp;T 对了,我把它和 Rustonomicon 中的例子弄混了。

以上是关于终身省略是不是适用于 trait impls 中的方法?的主要内容,如果未能解决你的问题,请参考以下文章

什么是非常简单的终身省略?

Rust宏接受类型与通用参数

[易学易懂系列|rustlang语言|零基础|快速入门|(14)]

是否可以在特征定义中使用“impl Trait”作为函数的返回类型?

我如何让impl Trait使用适当的生命周期来对其中有另一个生命周期的值进行可变引用?

有没有办法递归地压扁元组?