Reputation: 8428
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
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
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