简体   繁体   English

为dyn对象实现特征时的神秘生命问题

[英]Mysterious lifetime issue while implementing trait for dyn object

Consider the following toy example: 考虑以下玩具示例:

use std::cmp::Ordering;

pub trait SimpleOrder {
    fn key(&self) -> u32;
}

impl PartialOrd for dyn SimpleOrder {
    fn partial_cmp(&self, other: &dyn SimpleOrder) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

impl Ord for dyn SimpleOrder {
    fn cmp(&self, other: &dyn SimpleOrder) -> Ordering {
        self.key().cmp(&other.key())
    }
}

impl PartialEq for dyn SimpleOrder {
    fn eq(&self, other: &dyn SimpleOrder) -> bool {
        self.key() == other.key()
    }
}

impl Eq for SimpleOrder {}

This doesn't compile. 这不编译。 It claims there is a lifetime issue in the implementation for partial_cmp : 它声称在partial_cmp的实现中存在生命周期问题:

error[E0495]: cannot infer an appropriate lifetime due to conflicting requirements
 --> src/main.rs:9:23
  |
9 |         Some(self.cmp(other))
  |                       ^^^^^
  |
note: first, the lifetime cannot outlive the anonymous lifetime #2 defined on the method body at 8:5...
 --> src/main.rs:8:5
  |
8 | /     fn partial_cmp(&self, other: &dyn SimpleOrder) -> Option<Ordering> {
9 | |         Some(self.cmp(other))
10| |     }
  | |_____^
note: ...so that the declared lifetime parameter bounds are satisfied
 --> src/main.rs:9:23
  |
9 |         Some(self.cmp(other))
  |                       ^^^^^
  = note: but, the lifetime must be valid for the static lifetime...
  = note: ...so that the types are compatible:
          expected std::cmp::Eq
             found std::cmp::Eq

I really don't understand this error. 我真的不明白这个错误。 In particular "expected std::cmp::Eq found std::cmp::Eq " is puzzling. 特别是“预期std::cmp::Eq发现std::cmp::Eq令人费解。

If I inline the call manually it compiles fine: 如果我手动内联调用它编译正常:

fn partial_cmp(&self, other: &dyn SimpleOrder) -> Option<Ordering> {
    Some(self.key().cmp(&other.key()))
}

What's going on here? 这里发生了什么?

Trait object types have an associated lifetime bound, but it can be omitted. 特征对象类型具有关联的生命周期界限,但可以省略。 A full trait object type is written dyn Trait + 'a (when behind a reference, parentheses must be added around it: &(dyn Trait + 'a) ). 一个完整的特征对象类型被写成dyn Trait + 'a (当在引用后面时,必须在它周围添加括号: &(dyn Trait + 'a) )。

The tricky part is that when a lifetime bound is omitted, the rules are a bit complicated . 棘手的部分是当省略生命界限时, 规则有点复杂

First, we have: 首先,我们有:

impl PartialOrd for dyn SimpleOrder {

Here, the compiler infers + 'static . 这里,编译器推断出+ 'static Lifetime parameters are never introduced on impl blocks (as of Rust 1.32.0). 永远不会在impl块上引入生命周期参数(从Rust 1.32.0开始)。

Next, we have: 接下来,我们有:

    fn partial_cmp(&self, other: &dyn SimpleOrder) -> Option<Ordering> {

The type of other is inferred to be &'b (dyn SimpleOrder + 'b) , where 'b is an implicit lifetime parameter introduced on partial_cmp . other类型被推断为&'b (dyn SimpleOrder + 'b) ,其中'b是在partial_cmp引入的隐式生存期参数。

    fn partial_cmp<'a, 'b>(&'a self, other: &'b (dyn SimpleOrder + 'b)) -> Option<Ordering> {

So now we have that self has type &'a (dyn SimpleOrder + 'static) while other has type &'b (dyn SimpleOrder + 'b) . 所以现在我们有self类型&'a (dyn SimpleOrder + 'static)other类型&'b (dyn SimpleOrder + 'b) What's the problem? 有什么问题?

Indeed, cmp doesn't give any error, because its implementation doesn't require that the lifetime of the two trait objects be equal. 实际上, cmp不会给出任何错误,因为它的实现并不要求两个特征对象的生命周期相等。 Why does partial_cmp care, though? 为什么partial_cmp关心呢?

Because partial_cmp is calling Ord::cmp . 因为partial_cmp正在调用Ord::cmp When type checking a call to a trait method, the compiler checks against the signature from the trait. 当类型检查对trait方法的调用时,编译器会检查特征的签名。 Let's review that signature: 让我们来看看签名:

pub trait Ord: Eq + PartialOrd<Self> {
    fn cmp(&self, other: &Self) -> Ordering;

The trait requires that other be of type Self . 该特征要求other类型为Self That means that when partial_cmp calls cmp , it tries to pass a &'b (dyn SimpleOrder + 'b) to a parameter that expects a &'b (dyn SimpleOrder + 'static) , because Self is dyn SimpleOrder + 'static . 这意味着当partial_cmp调用cmp ,它会尝试将&'b (dyn SimpleOrder + 'b)传递给期望&'b (dyn SimpleOrder + 'static) ,因为Selfdyn SimpleOrder + 'static This conversion is not valid ( 'b cannot be converted to 'static ), so the compiler gives an error. 此转换无效( 'b无法转换为'static ),因此编译器会出错。

So then, why is it valid to set the type of other to &'b (dyn SimpleOrder + 'b) when implementing Ord ? 那么,为什么在实现Ord时将other类型设置为&'b (dyn SimpleOrder + 'b)是否有效? Because &'b (dyn SimpleOrder + 'b) is a supertype of &'b (dyn SimpleOrder + 'static) , and Rust lets you replace a parameter type with one of its supertypes when implementing a trait method (it makes the method strictly more general, even though it's apparently not used much in type checking). 因为&'b (dyn SimpleOrder + 'b)&'b (dyn SimpleOrder + 'static)超类型 ,而Rust允许你在实现特征方法时用一个超类型替换参数类型(它严格地使方法)更一般,即使它在类型检查中显然没有太多用处)。


In order to make your implementation as generic as possible, you should introduce a lifetime parameter on the impl s: 为了使您的实现尽可能通用,您应该在impl上引入一个生命周期参数:

use std::cmp::Ordering;

pub trait SimpleOrder {
    fn key(&self) -> u32;
}

impl<'a> PartialOrd for dyn SimpleOrder + 'a {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

impl<'a> Ord for dyn SimpleOrder + 'a {
    fn cmp(&self, other: &Self) -> Ordering {
        self.key().cmp(&other.key())
    }
}

impl<'a> PartialEq for dyn SimpleOrder + 'a {
    fn eq(&self, other: &Self) -> bool {
        self.key() == other.key()
    }
}

impl<'a> Eq for dyn SimpleOrder + 'a {}

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

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