Correct lifetime for struct constructed from borrows to struct

Hi,

I’m trying to write a trait that builds a struct from borrows of the fields of the implmenter, e.g.

// pseudo code
// error handling omitted for brevity

trait Collector {
  type Key;
  type Collection: 'a; // would require to add 'a to trait signature 
                       // but lifetime depends on borrow instead of implementer

  fn collect<'a>(&'a self, key: &Self::Key) -> Self::Collection<'a>
}

struct Example {
  values: Vec<u32>
}

impl Collector for Example {
  type Key = (usize, usize);
  type Collection = (&'a u32, &'a u32); // Here the problem is obvious

  fn collect<'a>(&'a self, key: &Self::Key) -> Self::Collection<'a> {
    (&self.values[key.0], &self.values[key.1])
  }
}

As you can see my proble is that my assosiated type requires a lifetime but this lifetime originates from the method call not from trait implementation.
I tried a bit with higher-kinded-lifetimes (i.e. for<'a>) but I don’t realy figure out how to use these.

Hi, would that be good?

Well, that would be a solution but I have two problems with that:

  1. This adds a lifetime to the trait so my code gets spoiled with lifetimes everywhere.
  2. The trait’s lifetime expresses wrong semantics. If a trait has a lifetime-parameter it suggests that the implementer holds some reference, meaning that the lifetime is at least as long as the implementer’s. But the lifetime I want to express is only for the Collection I produce.

An example: When collect() is called the Collection must live as long as the Collector. This means I can no longer end the borrow of Collector by dropping the Collection because they have the same lifetime. As a result Collector can not be mutated after the first call of collect() even if the Collection is no longer in use!

Therefore, I want the lifetime tied to the Collection rather than the Collector.

BTW: I know this could be easily done with GATs but… well… we all know…

For the first issue, I can’t do anything but I don’t understand the second one.

Not necessarily, in your case it’s the opposite, it’s there to introduce a lifetime.

fn main() {
    let mut example = Example {
        values: vec![0, 1, 2],
    };
    let tuple = example.collect(&(0, 2));
    dbg!(tuple);
    example.values.push(5);
}

And as you can see, you can modify Example as soon as Collection is no longer used.

Ok, I see you’re right :sweat_smile:. My actual use case is way more complicated and I’m using values of the Collection as Keys for further querying of the Collector, seemingly I have some very specific case that can’t be reproduced by such a simple example.
Nevertheless, I solved the problem myself by removing the assosiate Collection. I had only two of them and now I have two Collector-traits either. This is not very nice but works until we have GATs…

What are GATs?