Jeremy Salwen
Jeremy Salwen

Reputation: 8428

Trait which returns iterator with lifetime bounded by the lifetime of an argument

I have a trait which says that any implementation of Foo needs to provide a method bar which returns an object of some type which implements Iterator<Item = u32>:

trait Foo {
    type FooIterator: Iterator<Item = u32>;
    fn bar(&self) -> FooIterator;
}

For this case, I believe that the default lifetime elision means that the iterator returned by bar is required to live on its own, without being tied to the lifetime of the Foo it is iterating over. User Habnabit on #rust irc suggested the following way to say that the lifetime of the FooIterator is less than the lifetime of the Foo. i.e. it allows the implementation of the FooIterator to keep a reference to the Foo that it comes from:

trait Foo<'a> {
    type FooIterator: Iterator<Item = u32> + 'a;
    fn bar<'b: 'a>(&'b self) -> Self::FooIterator;
}

What I really want is the case where the function bar takes an additional argument, and the implementation of FooIterator is allowed to keep a reference to both the Foo and the additional argument. i.e. the lifetime of FooIterator is bounded by the lifetime of the Foo and the lifetime of the additional argument.

My literal translation of this idea would be

trait Zip {}
trait Foo<'a, 'c> {
    type FooIterator: Iterator<Item = u32> + 'a + 'c;
    // Foo.bar() returns an iterator that has a lifetime less than the Foo
    fn bar<'b: 'a, 'd: 'c>(&'b self, &'d Zip) -> Self::FooIterator;
}

But I was told there there is no "good" way to do this. What would be the best way to implement this idiom? What would the above code do exactly?

Upvotes: 4

Views: 553

Answers (2)

trent
trent

Reputation: 28075

Depending on how you want to use this trait, you may be able to make it work by implementing it for &'a Struct instead of for Struct, thus "hoisting" the responsibility for finding the right lifetime from the trait into the caller.

Remove the lifetime annotation from the trait and change bar so it takes self, plus another argument of the same lifetime:

trait Foo {
    type FooIterator: Iterator<Item = u32>;
    fn bar(self, other: Self) -> Self::FooIterator;
}

(Removing 'a from the trait is possible because bar consumes the reference instead of reborrowing it -- self doesn't have to outlive the return value anymore because it's been moved into it.)

Then impl it for a reference of lifetime 'a:

impl<'a> Foo for &'a Vec<u32> {
    type FooIterator = ...; // something presumably containing 'a
    fn bar(self, other: Self) -> Self::FooIterator {
        ...
    }
}

This works because the compiler can limit the lifetime 'a to one for which the impl applies.

Here's a playground link where bar is basically a wrapper around .chain().

I'm ignoring the Zip trait for now because how to incorporate it depends on what it provides. Instead, I suppose that bar only accepts an argument of the same type as Self. However, you can probably add it as well, maybe using the same technique if you need to.

Upvotes: 2

Francis Gagn&#233;
Francis Gagn&#233;

Reputation: 65935

What you're looking for is associated type constructors, a planned feature that is not yet implemented in Rust. With associated type constructors, your code would look like this:

trait Zip {}
trait Foo {
    type FooIterator<'a, 'c>: Iterator<Item = u32> + 'a + 'c;
    // Foo.bar() returns an iterator that has a lifetime less than the Foo
    fn bar<'a, 'b: 'a, 'c, 'd: 'c>(&'b self, &'d Zip) -> Self::FooIterator<'a, 'c>;
}

Actually, I'm not sure all those lifetimes are necessary, because a &'a T can be coerced to a &'b T where 'a: 'b. Thus, the following might be good enough:

trait Zip {}
trait Foo {
    type FooIterator<'a, 'c>: Iterator<Item = u32> + 'a + 'c;
    // Foo.bar() returns an iterator that has a lifetime less than the Foo
    fn bar<'a, 'c>(&'a self, &'c Zip) -> Self::FooIterator<'a, 'c>;
}

Upvotes: 4

Related Questions