【问题标题】: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; 类型必须具有相同生命周期的期望语义。

【问题讨论】:

    标签: rust lifetime


    【解决方案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;呢?

    另请参阅WikipediaRustonomicon 了解方差。

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

    '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 中的例子弄混了。
    猜你喜欢
    • 2018-01-23
    • 2017-03-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-10-11
    • 1970-01-01
    • 2014-09-28
    • 1970-01-01
    相关资源
    最近更新 更多