简体   繁体   English

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

[英]Does lifetime elision work for methods in trait impls?

With this question I am looking for feedback from people who have more knowledge in this area. 有了这个问题,我正在寻找那些在这方面有更多知识的人的反馈。 I am by no means an expert. 我绝不是专家。 So I might as well ask my question upfront: Is my reasoning correct here? 所以我不妨提前问我的问题:我的推理是否正确?

The problem 问题

Based on the answer to a question here on SO, I was confused to see the lifetime elided in the implementation of a trait method: 根据这里关于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
    }
}

Here, in the method signature the lifetime 'b was omitted on the type of other . 这里,在方法签名中,在other类型上省略了生命周期'b This works and is correct. 这是有效的,也是正确的。 I expected it to be &RefEquality<'b, T> for the type to be correct. 我期望它是&RefEquality<'b, T> ,因为类型是正确的。 After all, the 'b here is essential: The lifetime has to be different from 'a . 毕竟,这里的'b是必不可少的:生命必须与'a不同。 If not, it would be too restrictive: The implementation would only work for another RefEquality<T> with the same lifetime as Self . 如果没有,那就太限制了:实现只适用于另一个与Self相同的RefEquality<T> So those are obviously different semantics. 所以这些显然是不同的语义。 How can the compiler infer the correct lifetime? 编译器如何推断出正确的生命周期?

Lifetime elision takes care of it 终身消费照顾它

Lifetimes on function signatures can be elided but they cannot be elided on impl blocks. 可以省略函数签名的生命周期,但不能在impl块上省略它们。 There, the types have to be fully specified which includes naming lifetimes. 在那里,必须完全指定类型,包括命名生命周期。

On the eq() method on the other hand, I am able to elide the lifetime in the type annotation of other. 另一方面,在eq()方法上,我能够在其他类型注释中忽略生命周期。 In fact, the compiler then inserts an arbitrary lifetime for it which is obviously different from 'a . 实际上,编译器然后为它插入任意生命周期,这明显不同于'a That is the reason why this works while also keeping the same semantics: 这就是为什么这样做同时保持相同语义的原因:

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

Here, I introduced an arbitrary lifetime 'c for the method, which is basically the same the compiler does in case of lifetime elision. 在这里,我为该方法引入了一个任意的生命周期'c ,这与生命周期省略时的编译器基本相同。

Naming a lifetime 'b in my trait impl just stated that it has to be different from 'a (I also not linked them in any way). 在我的特质impl中命名一辈子'b只是声明它必须与'a (我也没有以任何方式链接它们)不同。 It follows logically, that this does not work: 从逻辑上讲,这不起作用:

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

I said in on the impl the types would be different (based on their lifetimes) but now the actual eq() implementation says they are the same. 我在impl中说过类型会有所不同(基于它们的生命周期),但现在实际的eq()实现说它们是相同的。 This results in a type error as expected. 这会导致预期的类型错误。

What if I want the lifetimes to be equal? 如果我希望生命时间平等怎么办? Can I still use lifetime elision in this case, or will the compiler insert an arbitrary lifetime and report a type error? 在这种情况下,我仍然可以使用生命周期省略,还是编译器会插入任意生命周期并报告类型错误? It turns out, the inference works correctly here as well: 事实证明,推理在这里也能正常工作:

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

The elided lifetime will be inferred to be 'a , keeping the desired semantics that both RefEquality<T> types have to have the same lifetime. 省略的生命周期将被推断为'a ,保持RefEquality<T>类型必须具有相同生命期望的期望语义。

Let's look at rustc's process to determine if a provided impl method corresponds to the signature declared in the trait. 让我们看看rustc的过程,以确定提供的impl方法是否对应于特征中声明的签名。

The location in the code is compare_impl_method in librustc_typeck/check/compare_method.rs , and it's well commented, however even the comments are hard to use for those that are not compiler hackers. 在代码中的位置compare_impl_methodlibrustc_typeck/check/compare_method.rs ,它的很好的注释,但连评论都难以用于那些没有编译器的黑客。

I'm not a compiler developer, so the following is based on my rust experience and interpretation! 我不是编译器开发人员,所以以下是基于我的生锈经验和解释!

The declaration in the trait corresponds to a particular function type, and the definition in the impl block is parsed to its own function type. 特征中的声明对应于特定的函数类型,并且impl块中的定义被解析为其自己的函数类型。

For this question I think only the conclusion of the type checking is important: 对于这个问题,我认为只有类型检查的结论才是重要的:

  • “Is the impl function a subtype of the trait function?” impl函数是特质函数的子类型吗?”

Subtyping I 对我进行分类

If S is a subtype of T , the subtyping relation is often written S <: T, to mean that any term of type S can be safely used in a context where a term of type T is expected. 如果S是T的子类型,则子类型关系通常写为S <:T,意味着可以在期望类型为T的项的上下文中安全地使用类型S的任何项。

It sounds reasonable. 这听起来很合理。 We want the impl block to define a function that can be used safely as if it is the function declared in the trait. 我们希望impl块定义一个可以安全使用的函数,就好像它是在trait中声明的函数一样。

Case 1 情况1

This is the elided lifetime case, but spelled out explicitly. 这是完整的终生案例,但明确说明。 I have replaced all method bodies with just a panic to emphasize that function signature checking is completely uninfluenced by the body of the function. 我已经用恐慌替换了所有方法体,以强调函数签名检查完全不受函数体的影响。

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

The trait expects a function of type: 该特征需要一个类型的函数:

fn(&RefEquality<'a, T>, &RefEquality<'b, T>)

You provide a function of type: 您提供类型的功能:

fn<'c>(&RefEquality<'a, T>, &RefEquality<'c, T>)

It looks like the provided impl is “more general” than required. 看起来所提供的impl比所需的“更通用”。 With 'c == 'b , then the function is of equal type. 使用'c == 'b ,则函数的类型相同。

It is a subtype of the expected type, because we can always use the fn<'c> version safely in its place. 它是预期类型的​​子类型,因为我们总是可以安全地使用fn<'c>版本。

Case 2 案例2

For your second example, that didn't compile: 对于你的第二个例子,那个没有编译:

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

You can add a bound 'b: 'a ('b outlives 'a), and then it's ok : 你可以添加一个绑定的'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!()
    }
}

The trait expects a function of type: 该特征需要一个类型的函数:

fn(&RefEquality<'a, T>, &RefEquality<'b, T>)

You provide a function of type: 您提供类型的功能:

fn(&RefEquality<'a, T>, &RefEquality<'a, T>)

I think it seems logical that they are compatible if 'b outlives 'a, but let's look at it calmly. 我认为,如果'b outlives'a,它们是兼容的似乎是合乎逻辑的,但让我们冷静地看待它。

Let's remove the constant factors: 让我们删除常数因素:

The trait expects a function of type: 该特征需要一个类型的函数:

fn(Ref<'b>)

You provide a function of type: 您提供类型的功能:

fn(Ref<'a>)

We also have the information that where 'b: 'a . 我们还有where 'b: 'a How can we see that these are compatible? 我们怎么能看到它们兼容?

Subtyping II: Attack of the Variances 子类型II:方差的攻击

Subtyping: is it safe to use X instead of Y ? 子类型:使用X代替Y是否安全?

Variance: if X is a subtype of Y , what about Foo<X> and Foo<Y> ? 方差: 如果 XY的子类型,那么Foo<X>Foo<Y>呢?

See also Wikipedia , Rustonomicon on variance. 另见WikipediaRustonomicon on variance。

The subtyping definition for lifetimes is: 生命周期的子类型定义是:

'x <: 'y means that 'x is longer than 'y . 'x <: 'y表示'x长于'y

Let's practice subtyping and variance with references. 让我们练习使用引用的子类型和方差。

When is it safe to use &'x i32 instead of &'y i32 ? 什么时候使用&'x i32而不是&'y i32是否&'y i32

It is when 'x is longer lived than 'y , then it's safe to replace. 'x'y更长寿时,那么更换是安全的。 'x living longer than 'y implies that &'x i32 is a subtype of &'y i32 : 'x生命长于'y意味着&'x i32&'y i32的子类型:

'x <: 'y => &'x i32 <: &'y i32

The subtyping relation is propagated in the same direction, and this is called covariance ; 子类型关系在同一方向传播,这称为协方差 ; &'a i32 is covariant in the 'a parameter. &'a i32'a参数中是协变的。

The variance behavior of a function instead is this: 相反,函数的方差行为是这样的:

X <: Y => fn(Y) <: fn(X)

Functions behave in the opposite way of their argument types. 函数的行为方式与其参数类型相反。 This is contravariance , logically “contra” since it's the opposite direction. 这是逆转 ,逻辑上“反对”,因为它是相反的方向。

Computation 计算

For this question we assume that Ref<'a> behaves as if it contains a &'a reference, and that it has the same variance as &'a itself has. 对于这个问题,我们假设Ref<'a>行为就好像它包含一个&'a引用,并且它&'a本身具有相同的方差

We were given the bound where 'b: 'a , which means: 我们被赋予了where 'b: 'a的界限,这意味着:

'b <: 'a

Use the covariance rule for references and Ref: 使用协方差规则进行参考和参考:

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

Use the contravariant rule for functions ** 对函数使用逆变规则**

Ref<'b> <: Ref<'a> => fn(Ref<'a>) <: fn(Ref<'b>)

And this was the question that rustc asked, is the impl function a subtype of the trait function. 这就是rustc问的问题, impl函数是特质函数的一个子类型。 It is! 它是!

** wrt function arguments : ** wrt函数参数

What if I want the lifetimes to be equal? 如果我希望生命时间平等怎么办?

If your goal is just to define PartialEq for the equal lifetime case, then yes, the elided lifetime case is fine. 如果你的目标只是为相同的终生案例定义PartialEq ,那么是的, PartialEq lifetime case就可以了。 It provides a more general function in the impl, but the type checker determines that it is compatible. 它在impl中提供了更通用的功能,但类型检查器确定它是兼容的。

You can also change the variance of your RefEquality type with respect to the lifetime parameter. 您还可以根据生命周期参数更改RefEquality类型的方差。

If you want a RefEquality<'a, T> is only subtype compatible with exactly the same lifetime, that's called invariance . 如果你想要一个RefEquality<'a, T>只是与完全相同的生命周期兼容的子类型,那就叫做不变性

There's a primitive you can use that has invariance, std::cell::Cell<T> . 你可以使用一个具有不变性的原语, std::cell::Cell<T> Cell<T> is invariant in the T parameter. Cell<T>T参数中是不变的。

The usual way to accomplish this is a PhantomData member: 完成此操作的常用方法是PhantomData成员:

struct RefEquality<'a, T: 'a> {
    ptr: &'a T,
    marker: PhantomData<Cell<&'a ()>>,
}

If you want to see an application of invariance, check out the crossbeam crate and how Scope<'a> being invariant in the 'a parameter is the cornerstone in its peculiar borrow rules for safe scoped threads. 如果你想看到一个不变量的应用,请查看crossbeam crate以及'a参数中不变量的Scope<'a>其安全范围线程的特殊借用规则的基石。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM