1

I have two methods on a struct.

The first method is a search which expects a criteria, and will return an iterator that applies that criteria:

pub fn search<'s>(
    &'s self,
    criteria: impl Criteria + 's,
) -> impl Iterator<Item = &'s Record> + 's {
    self.records.iter().filter(move |&tr| criteria.apply(tr))
}

I will admit to being a little confused as to why criteria has to have an explicit lifetime, because I move ownership of criteria into the filter closure. I would imagine it becomes owned by the Iterator, and thus have the same lifetime.

But, it works. Then I add on this function:

pub fn search_sorted<C, CMP>(&self, criteria: C, compare: CMP) -> Vec<(&UniqueId, &T)>
where
    C: Criteria,
    CMP: FnMut(&(&UniqueId, &T), &(&UniqueId, &T)) -> Ordering,
{
    let search_iter = self.search(criteria);
    let mut records: Vec<(&UniqueId, &T)> = search_iter.collect();
    records.sort_by(compare);
    records
}

Here the compiler insists that I need a lifetime on criteria. This confuses me, because criteria is an owned parameter, and then I'm passing ownership on to self.search. When I try to take the advice of the compiler to add a lifetime annotation, the compiler then just asks for more.

Here's a complete example that I built in Rust Playground:

use std::cmp::Ordering;

struct Record {}

trait Criteria {
    fn apply(&self, r: &Record) -> bool;
}

struct List {
    records: Vec<Record>
}

impl List {    
    fn new(lst: Vec<Record>) -> List {
        List{ records: lst }
    }
    
    pub fn search<'s>(
        &'s self,
        criteria: impl Criteria + 's,
    ) -> impl Iterator<Item = &'s Record> + 's {
        self.records.iter().filter(move |&tr| criteria.apply(tr))
    }
    
    pub fn search_sorted<C, CMP>(&self, criteria: C, compare: CMP) -> Vec<&Record>
    where
        C: Criteria,
        CMP: FnMut(&&Record, &&Record) -> Ordering,
    {
        let search_iter = self.search(criteria);
        let mut records: Vec<&Record> = search_iter.collect();
        records.sort_by(compare);
        records
    }

}

And then the error that I get (much forshortened) is this:

error[E0311]: the parameter type `C` may not live long enough
  --> src/lib.rs:30:32
   |
25 |     pub fn search_sorted<C, CMP>(&self, criteria: C, compare: CMP) -> Vec<&Record>
   |                          - help: consider adding an explicit lifetime bound...: `C: 'a`
...
30 |         let search_iter = self.search(criteria);
   |                                ^^^^^^
   |
note: the parameter type `C` must be valid for the anonymous lifetime #1 defined on the method body at 25:5...
  --> src/lib.rs:25:5

Can you give any advice for resolving this? For me, the obvious solution would just be "callers are required to do their own sorting", and I won't rule that out, but I do like having this utility function.

Peter Hall
  • 53,120
  • 14
  • 139
  • 204
Savanni D'Gerinel
  • 2,379
  • 17
  • 27

1 Answers1

2

I will admit to being a little confused as to why criteria has to have an explicit lifetime, because I move ownership of criteria into the filter closure.

From the function's perspective, Criteria is a trait, that could have any number of implementations and any of those implementations might contain references. This annotation makes sure that, if the implementation of Criteria does contain a reference, its lifetime must be longer than the call to search. Otherwise it can't guarantee that an arbitrary criteria is valid throughout the body of the function.

The same is also true for search_sorted. You just need to add the lifetime parameter, as the error message suggested:

pub fn search_sorted<'s, C, CMP>(&'s self, criteria: C, compare: CMP) -> Vec<&Record>
where
    C: Criteria + 's,
    CMP: FnMut(&&Record, &&Record) -> Ordering,
{
    let search_iter = self.search(criteria);
    let mut records: Vec<&Record> = search_iter.collect();
    records.sort_by(compare);
    records
}

Note that I've also constrained &self with the same lifetime. This is the connection that allows the compiler to guarantee that the criteria does not contain references with a lifetime shorter than the function body. This in turn allows the compiler to guarantee that it can guarantee the lifetime requirements of search when it is called by search_sorted.

Peter Hall
  • 53,120
  • 14
  • 139
  • 204
  • Egads. I was sure I had done exactly that, but it seems that I missed some aspect of the lifetime when I was trying things. Must have been `&'s self` on `search_sorted`. Thank you. Instant resolve. – Savanni D'Gerinel Oct 25 '20 at 02:33
  • Commenting solely to link to [The compiler suggests I add a 'static lifetime because the parameter type may not live long enough, but I don't think that's what I want](https://stackoverflow.com/questions/40053550/the-compiler-suggests-i-add-a-static-lifetime-because-the-parameter-type-may-no) which is the other, albeit less general way to solve this (make `C: 'static`). – trent Oct 25 '20 at 02:39
  • I've now had a lot more hours to think about this, and the entire concept that the trait implementation might contain references is something that escaped me. It's a really helpful point. – Savanni D'Gerinel Oct 25 '20 at 14:56