[英]Does lifetime elision work for methods in trait impls?
有了这个问题,我正在寻找那些在这方面有更多知识的人的反馈。 我绝不是专家。 所以我不妨提前问我的问题:我的推理是否正确?
根据这里关于SO 的问题的答案 ,我很困惑地看到在实施特征方法时终生没有了:
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
。 这是有效的,也是正确的。 我期望它是&RefEquality<'b, T>
,因为类型是正确的。 毕竟,这里的'b
是必不可少的:生命必须与'a
不同。 如果没有,那就太限制了:实现只适用于另一个与Self
相同的RefEquality<T>
。 所以这些显然是不同的语义。 编译器如何推断出正确的生命周期?
可以省略函数签名的生命周期,但不能在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
}
}
省略的生命周期将被推断为'a
,保持RefEquality<T>
类型必须具有相同生命期望的期望语义。
让我们看看rustc的过程,以确定提供的impl
方法是否对应于特征中声明的签名。
在代码中的位置compare_impl_method
在librustc_typeck/check/compare_method.rs
,它的很好的注释,但连评论都难以用于那些没有编译器的黑客。
我不是编译器开发人员,所以以下是基于我的生锈经验和解释!
特征中的声明对应于特定的函数类型,并且impl
块中的定义被解析为其自己的函数类型。
对于这个问题,我认为只有类型检查的结论才是重要的:
impl
函数是特质函数的子类型吗?” 如果S是T的子类型,则子类型关系通常写为S <:T,意味着可以在期望类型为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<'c>
版本。
对于你的第二个例子,那个没有编译:
impl<'a, 'b, T> PartialEq<RefEquality<'b, T>> for RefEquality<'a, T> {
fn eq(&self, other: &RefEquality<'a, T>) -> bool {
panic!()
}
}
你可以添加一个绑定的'b: 'a
('b outlives'a),然后就可以了 :
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 outlives'a,它们是兼容的似乎是合乎逻辑的,但让我们冷静地看待它。
让我们删除常数因素:
该特征需要一个类型的函数:
fn(Ref<'b>)
您提供类型的功能:
fn(Ref<'a>)
我们还有where 'b: 'a
。 我们怎么能看到它们兼容?
子类型:使用X
代替Y
是否安全?
方差: 如果 X
是Y
的子类型,那么Foo<X>
和Foo<Y>
呢?
另见Wikipedia , Rustonomicon on variance。
生命周期的子类型定义是:
'x <: 'y
表示'x
长于'y
。
让我们练习使用引用的子类型和方差。
什么时候使用&'x i32
而不是&'y 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>
对函数使用逆变规则**
Ref<'b> <: Ref<'a> => fn(Ref<'a>) <: fn(Ref<'b>)
这就是rustc问的问题, impl
函数是特质函数的一个子类型。 它是!
** wrt函数参数 :
如果你的目标只是为相同的终生案例定义PartialEq
,那么是的, PartialEq
lifetime case就可以了。 它在impl中提供了更通用的功能,但类型检查器确定它是兼容的。
您还可以根据生命周期参数更改RefEquality
类型的方差。
如果你想要一个RefEquality<'a, T>
只是与完全相同的生命周期兼容的子类型,那就叫做不变性 。
你可以使用一个具有不变性的原语, std::cell::Cell<T>
。 Cell<T>
在T
参数中是不变的。
完成此操作的常用方法是PhantomData
成员:
struct RefEquality<'a, T: 'a> {
ptr: &'a T,
marker: PhantomData<Cell<&'a ()>>,
}
如果你想看到一个不变量的应用,请查看crossbeam crate以及在'a
参数中不变量的Scope<'a>
是其安全范围线程的特殊借用规则的基石。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.