为动态对象实现特征时的神秘生命周期问题

Mysterious lifetime issue while implementing trait for dyn object

考虑以下玩具示例:

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

这无法编译。它声称 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

我真的不明白这个错误。特别是 "expected std::cmp::Eq found std::cmp::Eq" 令人费解。

如果我手动内联调用它编译正常:

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

这是怎么回事?

特征对象类型具有关联的生命周期界限,但可以省略。一个完整的 trait 对象类型写成 dyn Trait + 'a(在引用后面时,必须在其周围添加括号:&(dyn Trait + 'a))。

棘手的部分是当生命周期被省略时,the rules are a bit complicated.

首先,我们有:

impl PartialOrd for dyn SimpleOrder {

这里,编译器推断出+ 'staticimpl 块上从未引入生命周期参数(从 Rust 1.32.0 开始)。

接下来,我们有:

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

other 的类型被推断为 &'b (dyn SimpleOrder + 'b),其中 'b 是在 partial_cmp.

上引入的隐式生命周期参数
    fn partial_cmp<'a, 'b>(&'a self, other: &'b (dyn SimpleOrder + 'b)) -> Option<Ordering> {

所以现在 self 的类型为 &'a (dyn SimpleOrder + 'static),而 other 的类型为 &'b (dyn SimpleOrder + 'b)。有什么问题?

的确,cmp 不会给出任何错误,因为它的实现并不要求两个特征对象的生命周期相等。为什么 partial_cmp 会关心呢?

因为partial_cmp正在呼叫Ord::cmp。当对特征方法的调用进行类型检查时,编译器会检查特征的签名。让我们回顾一下那个签名:

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

特征要求 otherSelf 类型。这意味着当 partial_cmp 调用 cmp 时,它会尝试将 &'b (dyn SimpleOrder + 'b) 传递给需要 &'b (dyn SimpleOrder + 'static) 的参数,因为 Selfdyn SimpleOrder + 'static .这个转换无效('b不能转换成'static),所以编译器报错

那么,为什么在实现Ord时将other的类型设置为&'b (dyn SimpleOrder + 'b)有效呢?因为 &'b (dyn SimpleOrder + 'b)&'b (dyn SimpleOrder + 'static)supertype,并且 Rust 允许您在实现 trait 方法时用它的超类型之一替换参数类型(它使该方法更严格地通用,即使它是显然在类型检查中用得不多。


为了使您的实现尽可能通用,您应该在 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 {}