为 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(&self, other: &(dyn SimpleOrder + 'static)) -> Option<Ordering>
工作 ;)
【参考方案1】:
Trait 对象类型具有关联的生命周期限制,但可以省略。一个完整的 trait 对象类型写成 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
没有给出任何错误,因为它的实现不需要两个 trait 对象的生命周期相等。为什么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)
的参数,因为Self
是dyn SimpleOrder + 'static
。这个转换无效('b
不能转换成'static
),所以编译器报错。
那么,为什么在实现Ord
时将other
的类型设置为&'b (dyn SimpleOrder + 'b)
是有效的呢?因为&'b (dyn SimpleOrder + 'b)
是supertype 的&'b (dyn SimpleOrder + 'static)
,并且Rust 允许您在实现特征方法时将参数类型替换为其超类型之一(它使该方法更加通用,即使它显然在类型中使用得不多检查)。
为了使您的实现尽可能通用,您应该在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
【讨论】:
这是有道理的。我确实相信 Rust 生成的错误消息应该得到改进 - 这是问题并根本从错误消息中不清楚。 "所以现在我们的 self 的类型是 &(dyn SimpleOrder + 'a) 而其他的类型是 &(dyn SimpleOrder + 'static)。" ——这应该反过来吧? 令我惊讶的是,调用SimpleOrder::cmp(self, other)
不会检查SimpleOrder::cmp
的签名(会成功),而是会检查Ord::cmp
的签名(失败) .
@Chronial:我认为这将保证它自己的(单独的)问题;值得注意的是,我想在没有dyn Trait
的情况下也可以触发该行为,只需利用包含引用的常规类型的子类型。
@trentcl 我记得在某处读到&'a T
等于&'a (T + 'a)
。这也符合Box
的行为(非引用为'static
)。啊,找到参考了:doc.rust-lang.org/reference/…以上是关于为 dyn 对象实现特征时的神秘生命周期问题的主要内容,如果未能解决你的问题,请参考以下文章
为啥作为参数传递的特征对象的生命周期需要更高等级的特征边界,而结构不需要?
为结构实现特征时,为什么会出现“缺少生命周期说明符”或“错误的类型参数数”?
Spring -- Spring配置文件详解(Bean标签的基本配置(id,class)/ 范围配置 / 不同范围时的对象的创建时期 / Bean生命周期配置(生命周期方法) )