为动态对象实现特征时的神秘生命周期问题
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 {
这里,编译器推断出+ '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)
的参数,因为 Self
是 dyn 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 {}
考虑以下玩具示例:
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 {
这里,编译器推断出+ '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)
的参数,因为 Self
是 dyn 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 {}