NanoWizard
NanoWizard

Reputation: 2164

Why does Vec.sort() seem to require a static lifetime?

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 Vecs 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

Playground link

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

Answers (1)

pretzelhammer
pretzelhammer

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 Things 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();
}

playground


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

Related Questions