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

Posted

技术标签:

【中文标题】为 dyn 对象实现特征时的神秘生命周期问题【英文标题】:Mysterious lifetime issue while implementing trait for dyn object 【发布时间】:2019-06-17 03:48:48 【问题描述】:

考虑以下玩具示例:

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

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

如果我手动内联调用,它编译得很好:

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

这是怎么回事?

【问题讨论】:

神秘的! 既然我们在谈论特质...... 'static 可能在某个地方丢失了? @MatthieuM。为什么partial_cmp 的参数需要静态生命周期,而cmp 不需要? @PeterHall:我不知道,但我认为这可能是“预期的 std::cmp::Eq 找到 std::cmp::Eq”背后的线索,一个有一个@ 987654330@ 没有显示的生命周期,而另一个没有显示。我当然期待这个问题的答案:D fn partial_cmp(&amp;self, other: &amp;(dyn SimpleOrder + 'static)) -&gt; Option&lt;Ordering&gt; 工作 ;) 【参考方案1】:

Trait 对象类型具有关联的生命周期限制,但可以省略。一个完整的 trait 对象类型写成 dyn Trait + 'a(当引用后面时,必须在其周围添加括号:&amp;(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 的类型被推断为&amp;'b (dyn SimpleOrder + 'b),其中'b 是在partial_cmp 上引入的隐式生命周期参数。

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

所以现在我们有了self 的类型为&amp;'a (dyn SimpleOrder + 'static)other 的类型为&amp;'b (dyn SimpleOrder + 'b)。有什么问题?

确实,cmp 没有给出任何错误,因为它的实现不需要两个 trait 对象的生命周期相等。为什么partial_cmp 关心呢?

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

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

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

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


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

【讨论】:

这是有道理的。我确实相信 Rust 生成的错误消息应该得到改进 - 这是问题并根本从错误消息中不清楚。 "所以现在我们的 self 的类型是 &(dyn SimpleOrder + 'a) 而其他的类型是 &(dyn SimpleOrder + 'static)。" ——这应该反过来吧? 令我惊讶的是,调用SimpleOrder::cmp(self, other) 不会检查SimpleOrder::cmp 的签名(会成功),而是会检查Ord::cmp 的签名(失败) . @Chronial:我认为这将保证它自己的(单独的)问题;值得注意的是,我想在没有dyn Trait 的情况下也可以触发该行为,只需利用包含引用的常规类型的子类型。 @trentcl 我记得在某处读到&amp;'a T 等于&amp;'a (T + 'a)。这也符合Box 的行为(非引用为'static)。啊,找到参考了:doc.rust-lang.org/reference/…

以上是关于为 dyn 对象实现特征时的神秘生命周期问题的主要内容,如果未能解决你的问题,请参考以下文章

具有不同生命周期的对象的 Scala 蛋糕模式

使用refs实现迭代器时的生命周期推断问题

为啥作为参数传递的特征对象的生命周期需要更高等级的特征边界,而结构不需要?

为结构实现特征时,为什么会出现“缺少生命周期说明符”或“错误的类型参数数”?

Spring -- Spring配置文件详解(Bean标签的基本配置(id,class)/ 范围配置 / 不同范围时的对象的创建时期 / Bean生命周期配置(生命周期方法) )

创建返回实现 serde::Deserialize 的值的函数时的生命周期错误