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

orl*_*rlp 19 traits lifetime rust

考虑以下玩具示例:

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 {}
Run Code Online (Sandbox Code Playgroud)

这不编译.它声称在实施过程中存在一个终身问题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
Run Code Online (Sandbox Code Playgroud)

我真的不明白这个错误.特别是"预期std::cmp::Eq发现std::cmp::Eq"令人费解.

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

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

这里发生了什么?

Fra*_*gné 19

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

棘手的部分是当省略生命界限时,规则有点复杂.

首先,我们有:

impl PartialOrd for dyn SimpleOrder {
Run Code Online (Sandbox Code Playgroud)

这里,编译器推断+ 'static.永远不会在impl块上引入生命周期参数(从Rust 1.32.0开始).

接下来,我们有:

    fn partial_cmp(&self, other: &dyn SimpleOrder) -> Option<Ordering> {
Run Code Online (Sandbox Code Playgroud)

other推断的类型是&'b (dyn SimpleOrder + 'b),其中'b引入了隐式生命周期参数partial_cmp.

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

所以,现在我们有一个self已键入&'a (dyn SimpleOrder + 'static)other已输入&'b (dyn SimpleOrder + 'b).有什么问题?

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

因为partial_cmp是在打电话Ord::cmp.当类型检查对trait方法的调用时,编译器会检查特征的签名.让我们来看看签名:

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

特征要求other属于类型Self.这意味着当partial_cmp调用时cmp,它会尝试将a传递&'b (dyn SimpleOrder + 'b)给期望a的参数&'b (dyn SimpleOrder + 'static),因为Selfdyn SimpleOrder + 'static.此转换无效('b无法转换为'static),因此编译器会出错.

那么,为什么是有效设置的类型other&'b (dyn SimpleOrder + 'b)实施时Ord?由于&'b (dyn SimpleOrder + 'b)超类型&'b (dyn SimpleOrder + 'static),而锈让你与它的超类型之一替换参数类型实现特质方法时(这使得该方法严格比较一般,即使它显然不是用在类型检查多).


为了使您的实现尽可能通用,您应该在impls 上引入一个生命周期参数:

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 {}
Run Code Online (Sandbox Code Playgroud)

  • 那讲得通.我确实认为Rust生成的错误消息应该得到改善 - 这个问题根本不是*从错误消息中清除. (4认同)
  • 这里让我感到惊讶的是调用`SimpleOrder :: cmp(self,other)`不会检查`Si​​mpleOrder :: cmp`的签名(这会成功),但是反对`Ord ::的签名cmp`(失败了). (3认同)
  • @trentcl我记得在某处读过'&'一个T`等于'&'a(T +'a)`.这也符合`Box`的行为(非引用是''静态`).啊,找到了参考:https://doc.rust-lang.org/reference/lifetime-elision.html#default-trait-object-lifetimes (2认同)