Consider the following toy example:
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 {}
This doesn't compile. It claims there is a lifetime issue in the implementation for 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
I really don't understand this error. In particular "expected std::cmp::Eq
found std::cmp::Eq
" is puzzling.
If I inline the call manually it compiles fine:
fn partial_cmp(&self, other: &dyn SimpleOrder) -> Option<Ordering> {
Some(self.key().cmp(&other.key()))
}
What's going on here?
Trait object types have an associated lifetime bound, but it can be omitted. A full trait object type is written dyn Trait + 'a
(when behind a reference, parentheses must be added around it: &(dyn Trait + 'a)
).
The tricky part is that when a lifetime bound is omitted, the rules are a bit complicated.
First, we have:
impl PartialOrd for dyn SimpleOrder {
Here, the compiler infers + 'static
. Lifetime parameters are never introduced on impl
blocks (as of Rust 1.32.0).
Next, we have:
fn partial_cmp(&self, other: &dyn SimpleOrder) -> Option<Ordering> {
The type of other
is inferred to be &'b (dyn SimpleOrder + 'b)
, where 'b
is an implicit lifetime parameter introduced on partial_cmp
.
fn partial_cmp<'a, 'b>(&'a self, other: &'b (dyn SimpleOrder + 'b)) -> Option<Ordering> {
So now we have that self
has type &'a (dyn SimpleOrder + 'static)
while other
has type &'b (dyn SimpleOrder + 'b)
. What's the problem?
Indeed, cmp
doesn't give any error, because its implementation doesn't require that the lifetime of the two trait objects be equal. Why does partial_cmp
care, though?
Because partial_cmp
is calling Ord::cmp
. When type checking a call to a trait method, the compiler checks against the signature from the trait. Let's review that signature:
pub trait Ord: Eq + PartialOrd<Self> {
fn cmp(&self, other: &Self) -> Ordering;
The trait requires that other
be of type Self
. That means that when partial_cmp
calls cmp
, it tries to pass a &'b (dyn SimpleOrder + 'b)
to a parameter that expects a &'b (dyn SimpleOrder + 'static)
, because Self
is dyn SimpleOrder + 'static
. This conversion is not valid ('b
cannot be converted to 'static
), so the compiler gives an error.
So then, why is it valid to set the type of other
to &'b (dyn SimpleOrder + 'b)
when implementing Ord
? Because &'b (dyn SimpleOrder + 'b)
is a supertype of &'b (dyn SimpleOrder + 'static)
, and Rust lets you replace a parameter type with one of its supertypes when implementing a trait method (it makes the method strictly more general, even though it's apparently not used much in type checking).
In order to make your implementation as generic as possible, you should introduce a lifetime parameter on the 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 {}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With