Reputation: 2164
This is a vastly simplified example of the issue I'm running into, but given a trait Thing
which implements Ord
, and a struct Object
which implements Thing
, I have the following struct:
pub struct MyStruct<'a> {
my_things: HashMap<i32, Vec<Box<dyn Thing + 'a>>>
}
impl<'a> MyStruct<'a> {
pub fn new() -> MyStruct<'a> {
MyStruct {
my_things: HashMap::new()
}
}
pub fn add_object(&mut self, key: i32, obj: Object) {
if !self.my_things.contains_key(&key) {
self.my_things.insert(key, Vec::new());
}
let new_thing: Box<dyn Thing> = Box::new(obj);
let things = self.my_things.get_mut(&key).unwrap();
things.push(new_thing);
things.sort();
}
}
It essentially takes a key and an Object
, and adds the object to a HashMap
of Vec
s using the given key. I know this isn't the optimal way to do this but I wanted to keep it simpler for illustration.
The compiler complains on the call to things.sort()
with the following error:
error[E0308]: mismatched types
--> src/main.rs:58:16
|
58 | things.sort();
| ^^^^ lifetime mismatch
|
= note: expected trait `Ord`
found trait `Ord`
note: the lifetime `'a` as defined on the impl at 42:6...
--> src/main.rs:42:6
|
42 | impl<'a> MyStruct<'a> {
| ^^
= note: ...does not necessarily outlive the static lifetime
If I remove all of the 'a
lifetimes in this example, the code will compile. But for my actual use case, I need to allow non-static lifetimes.
Can someone explain what's going on here? Does sort()
really require the Vec
to contain items with static lifetimes? If so why?
Is there a good workaround?
Upvotes: 3
Views: 162
Reputation: 15135
You only implemented Ord
for dyn Thing + 'static
. The 'static
lifetime bound is inferred if you don't explicitly specify any other lifetime bound. To implement Ord
for non-'static
dyn Thing
s you need to introduce and use a generic lifetime parameter, e.g. 'a
, in your implementations. Updated compiling example:
use std::collections::HashMap;
use std::cmp;
pub trait Thing {
fn priority(&self) -> i32;
}
impl<'a> PartialEq for dyn Thing + 'a { // 'a added here
fn eq(&self, other: &Self) -> bool {
self.priority() == other.priority()
}
}
impl<'a> PartialOrd for dyn Thing + 'a { // 'a added here
fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
self.priority().partial_cmp(&other.priority())
}
}
impl<'a> Eq for dyn Thing + 'a {} // 'a added here
impl<'a> Ord for dyn Thing + 'a { // 'a added here
fn cmp(&self, other: &Self) -> cmp::Ordering{
self.priority().cmp(&other.priority())
}
}
pub struct Object {
priority: i32,
}
impl Thing for Object {
fn priority(&self) -> i32 {
self.priority
}
}
pub struct MyStruct<'a> {
my_things: HashMap<i32, Vec<Box<dyn Thing + 'a>>>
}
impl<'a> MyStruct<'a> {
pub fn new() -> MyStruct<'a> {
MyStruct {
my_things: HashMap::new()
}
}
pub fn add_object(&mut self, key: i32, obj: Object) {
if !self.my_things.contains_key(&key) {
self.my_things.insert(key, Vec::new());
}
let new_thing: Box<dyn Thing> = Box::new(obj);
let things = self.my_things.get_mut(&key).unwrap();
things.push(new_thing);
things.sort();
}
}
fn main() {
let _test = MyStruct::new();
}
From the Default Trait Object Lifetimes section of the Lifetime Elision chapter of the Rust Reference (emphasis mine):
If the trait object is used as a type argument of a generic type then the containing type is first used to try to infer a bound.
- If there is a unique bound from the containing type then that is the default
- If there is more than one bound from the containing type then an explicit bound must be specified
If neither of those rules apply, then the bounds on the trait are used:
- If the trait is defined with a single lifetime bound then that bound is used.
- If 'static is used for any lifetime bound then 'static is used.
- If the trait has no lifetime bounds, then the lifetime is inferred in expressions and is
'static
outside of expressions.
Upvotes: 6