终身省略是不是适用于 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
被省略了。这有效并且是正确的。我希望它是 &RefEquality<'b, T>
类型是正确的。毕竟,这里的'b
是必不可少的:生命周期必须不同于'a
。如果不是,那就太严格了:该实现只适用于另一个与Self
具有相同生命周期的RefEquality<T>
。所以这些显然是不同的语义。编译器如何推断正确的生命周期?
终身省略处理它
函数签名的生命周期可以省略,但不能在 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<T>
类型必须具有相同生命周期的期望语义。
【问题讨论】:
【参考方案1】:我们看一下rustc的过程,判断一个提供的impl
方法是否对应
到特征中声明的签名。
代码中的位置是
compare_impl_method
在librustc_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<'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
('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
是否安全?
方差: 如果X
是Y
的子类型,那么Foo<X>
和Foo<Y>
呢?
另请参阅Wikipedia、Rustonomicon 了解方差。
生命周期的子类型定义是:
'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>
对函数使用逆变规则**
Ref<'b> <: Ref<'a> => fn(Ref<'a>) <: fn(Ref<'b>)
这是 rustc 提出的问题,是 impl
函数
特征函数的子类型。是的!
** w.r.t.函数参数:
如果我希望生命周期相等怎么办?
如果您的目标只是为相等生命周期的情况定义PartialEq
,那么是的,省略的生命周期情况很好。它在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 ()>>,
如果您想查看不变性的应用,请查看
横梁板条箱以及Scope<'a>
being invariant in the 'a
parameter 如何成为其特殊借用规则的基石
安全范围的线程。
【讨论】:
这...是一个了不起的答案。感谢您显然为描述这一点所做的所有工作! 不应该是PhantomData<Cell<&'a T>>
而不是 PhantomData<Cell<&'a ()>>
以便对通用 T
有正确的删除语义吗?
&T
没有我知道的 dropck 含义,并且类型中已经有一个 &T
。
对了,我把它和 Rustonomicon 中的例子弄混了。以上是关于终身省略是不是适用于 trait impls 中的方法?的主要内容,如果未能解决你的问题,请参考以下文章
[易学易懂系列|rustlang语言|零基础|快速入门|(14)]
是否可以在特征定义中使用“impl Trait”作为函数的返回类型?