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

时间:2019-01-23 14:14:19

标签: rust traits lifetime

考虑以下玩具示例:

filtered_profile

这不能编译。它声称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

这是怎么回事?

1 个答案:

答案 0 :(得分:17)

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

棘手的部分是,如果忽略生命周期限制,则the rules are a bit complicated

首先,我们有:

impl PartialOrd for dyn SimpleOrder {

在这里,编译器会推断+ 'static。终生参数永远不会在impl块上引入(从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;

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

那么,为什么在实现other时将&'b (dyn SimpleOrder + 'b)的类型设置为Ord是有效的?因为&'b (dyn SimpleOrder + 'b)&'b (dyn SimpleOrder + 'static)的{​​{3}},并且Rust允许您在实现trait方法时用其超类型之一替换参数类型(严格来说,该方法更加通用)显然在类型检查中用的很少。)


为了使实现尽可能通用,应在impl s上引入生命周期参数:

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