Lifetime for associated type


#1

I am quite stuck with a lifetime problem. I tried searching around, but I always find similar situations, but not exactly this problem.

I want a simple trait with a get() function that returns an associated Item type:

trait TestExt {
    type Item;
    fn get(&self, index: usize) -> Self::Item;
}

Now I got a simple struct that implements this trait:

pub struct TestA<T>(Vec<T>);

impl<T: Copy> TestExt for TestA<T> {
    type Item = T;

    fn get(&self, index: usize) -> Self::Item {
        self.0[index]
    }
}

Notice that TestA want a Copyable parameter. I am obviously omitting many details, useless for this example.

Now imagine that I want to write a TestB, to be used with heavier objects that does not impl Copy, but only Clone (yes, I would like to use the specialization, but we have to wait :wink: ). Even if I could, I don’t want to return a T, but a &T. Something like

pub struct TestB<T>(Vec<T>);

impl<T> TestExt for TestB<T> {
    type Item = &/*missing lifetime!*/ T;

    fn get(&self, index: usize) -> Self::Item {
        &self.0[index]
    }
}

The question is: how can I bind the lifetime of the associated type Item to the lifetime of the object? Without having to impl TestExt, it’s straightforward because the lifetime of the returned &T is the same as &self. But in this case I don’t know how to handle it. I also tried to use a PhantomData marker, but without success.


#2

This is not currently possible. The missing feature is detailed in https://github.com/rust-lang/rfcs/blob/master/text/1598-generic_associated_types.md.

I would suggest you define your trait as

trait TestExt {
    type Item;
    fn get(&self, index: usize) -> &Self::Item;
}

For Copy types, you can deref to get a copy and disconnect the borrow.


#3

Thanks, you have been quite helpful.
Unfortunately my specific case is about using Num. The elements, obviously, must be normally taken by value. The problem arises when BigUint and BigInt are needed, because they are dynamically allocated and copying them is expensive.

In a generic context, using always reference is an overhead for basic numeric types, on the other hand with more complex numeric types it is the only possible choice. In my specific case, I can do exactly how you told me, because I do not need to perform many operations with the results.
Thank you again!


#4

It’s hard to conjecture about performance diffs of returning small types by-val vs by-ref because the optimizer may spoil any intuition we may have :slight_smile:.

The biggest downside to the approach I suggested is your TestExt is tied to returning only references and on top of that, associated with self (or 'static but that’s not very interesting). That may not be an issue for your case, but it’s a limitation that generic associated types will remove for these types of cases.


#5

I completely agree with you about being a big limitation. At to date, there are still many limitations with Rust, but it is evolving at a fascinating speed. I come from C++, and even if I was very sceptic at the beginning, I started loving Rust. Rust can already do what we will obtain in C++20 and, maybe, C++23. And probably the limitations we are having now will disappear in less than two years (maybe even before). We just need to be patient :wink:


#6

You might also soon run into issues like here: NDArrayView lifetimes [solved]