有了这个问题,我正在寻找那些对这方面有更多了解的人的反馈。我绝不是专家。所以我不妨提前问我的问题:我的推理是否正确?
根据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
。所以这些显然是不同的语义。编译器如何推断出正确的生命周期?
可以省略函数签名的生命周期,但不能在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>
类型必须具有相同生命周期的语义。
答案 0 :(得分:7)
让我们看看rustc的过程,以确定提供的impl
方法是否对应
到特征中声明的签名。
代码中的位置是
compare_impl_method
在librustc_typeck/check/compare_method.rs
中,评论很好,
然而,即使这些评论也很难用于那些不是编译器黑客的人。
我不是编译器开发人员,所以以下是基于我的生锈经验 和解释!
特征中的声明对应于特定的函数类型,
并且impl
块中的定义被解析为它自己的函数类型。
对于这个问题,我认为只有类型检查的结论很重要:
impl
函数是否是特质函数的子类型?”If S is a subtype of T,经常会写出子类型关系 S&lt;:T,意味着任何类型的S都可以 安全地使用在预期类型为T的术语中。
听起来很合理。我们希望impl
块定义一个函数
可以安全地使用,就像它是在特征中声明的函数一样。
这是完整的终生案例,但明确说明。 我已经用恐慌替换了所有方法体,以强调这一点 功能签名检查完全不受影响 功能的主体。
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>
版本
安全地取而代之。
对于你的第二个例子,它没有编译:
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
的信息。我们怎么能看到它们是兼容的?
子分类:使用X
代替Y
是否安全?
差异: 如果 X
是[{1}}的子类型,那么Y
和Foo<X>
呢?< / p>
另见Wikipedia,Rustonomicon方差。
生命周期的子类型定义是:
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如何成为其特殊借用规则的基石
安全范围的线程。