终身省略是否适用于特质的方法?

时间:2015-11-22 17:11:42

标签: rust lifetime

有了这个问题,我正在寻找那些对这方面有更多了解的人的反馈。我绝不是专家。所以我不妨提前问我的问题:我的推理是否正确?

问题

根据SO上的answer to a question,我很困惑地看到在实施特质方法时终生没有了:

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
    }
}

此处,在方法签名中,'b的类型省略了生命周期other。这是有效的,也是正确的。我希望它的类型为&RefEquality<'b, T>是正确的。毕竟,此处'b至关重要:生命周期必须与'a不同。如果不是,则限制性太强:实施仅适用于与RefEquality<T>具有相同生命期的另一个Self。所以这些显然是不同的语义。编译器如何推断出正确的生命周期?

Lifetime elision负责照顾它

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

另一方面,在eq()方法上,我能够在其他类型注释中忽略生命周期。实际上,编译器然后为它插入任意生命周期,这明显不同于'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,这与生命周期中的编译器基本相同。

在我的特质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
    }
}

elined lifetime将推断为'a,保持两个RefEquality<T>类型必须具有相同生命周期的语义。

1 个答案:

答案 0 :(得分:7)

让我们看看rustc的过程,以确定提供的impl方法是否对应 到特征中声明的签名。

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

我不是编译器开发人员,所以以下是基于我的生锈经验 和解释!

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

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

  • impl函数是否是特质函数的子类型?”

对我进行分类

  

If S is a subtype of T,经常会写出子类型关系   S&lt;:T,意味着任何类型的S都可以   安全地使用在预期类型为T的术语中。

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

<案例1

这是完整的终生案例,但明确说明。 我已经用恐慌替换了所有方法体,以强调这一点 功能签名检查完全不受影响 功能的主体。

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<'c>版本 安全地取而代之。

<案例2

对于你的第二个例子,它没有编译:

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

您可以添加约束'b: 'a(&#39; b outlives&#39; 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>)

我认为,如果&#39; b超过了a,它们是兼容的似乎是合乎逻辑的,但让我们冷静地看待它。

让我们删除常数因素:

该特征需要一个类型为

的函数
fn(Ref<'b>)

您提供类型的功能:

fn(Ref<'a>)

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

子类型II:方差的攻击

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

差异: 如果 X是[{1}}的子类型,那么YFoo<X>呢?< / p>

另见WikipediaRustonomicon方差。

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

Foo<Y>表示'x <: 'y长于'x

让我们练习使用引用进行子类型和方差。

何时使用'y代替&'x i32安全?

&'y i32的寿命比'x长时,它就是{。}} 安全更换。 'y生活时间超过'x意味着这一点 'y&'x i32的子类型:

&'y i32

子类型关系在相同的方向传播, 这叫做协方差; 'x <: 'y => &'x i32 <: &'y i32参数中的&'a i32 协变

函数的方差行为是:

'a

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

计算

对于这个问题,我们假设X <: Y => fn(Y) <: fn(X)表现为 如果它包含Ref<'a>引用,并且它具有相同的方差 正如&'a本身所有。

我们获得了绑定&'a,这意味着:

where 'b: 'a

使用协方差规则进行参考和参考:

'b <: 'a

对函数使用逆变规则**

'b <: 'a => Ref<'b> <: Ref<'a>

这就是rustc问的问题,是Ref<'b> <: Ref<'a> => fn(Ref<'a>) <: fn(Ref<'b>)函数 特质函数的子类型。它是!

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

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

如果您的目标只是为同等终身案例定义impl,那么是,退出的终生案例很好。它在impl中提供了更通用的功能,但类型检查器确定它是兼容的。

您还可以根据生命周期参数更改PartialEq类型的方差。

如果您希望RefEquality仅与子类型兼容 在完全相同的生命周期中,这被称为不变性

您可以使用具有不变性的基元RefEquality<'a, T>std::cell::Cell<T>参数中Cell<T>不变。

实现此目的的通常方法是T成员:

PhantomData

如果您想查看不变性应用,请查看 crossbeam crate以及Scope<'a> being invariant in the 'a parameter如何成为其特殊借用规则的基石 安全范围的线程。