I have a trait with an associated type, but I want to implement it with a reference type, however I can't get the lifetimes right.
For example, without references, suppose I have:
trait DoesStuff {
type Output;
fn do_thing(&mut self, x: f64) -> Self::Output;
}
struct SummingWidget {
sum_so_far: f64
}
impl SummingWidget {
pub fn new() -> Self {
Self{
sum_so_far: 0.0
}
}
}
impl DoesStuff for SummingWidget {
type Output = f64;
fn do_thing(&mut self, x: f64) -> Self::Output {
self.sum_so_far += x;
self.sum_so_far
}
}
Then all works fine. But suppose instead of returning the value, I just want to return a reference to the value held by the object. The obvious attempt of just returning the reference, results in request for a lifetime on the associated type.
type Output = &f64;
Adding a lifetime to the associated type then requires it added to the impl, and it is then unconstrained, so I try adding it to the struct, which then needs a PhantomData and I end up with:
struct SummingWidgetRef<'a> {
sum_so_far: f64,
phantom: PhantomData<&'a ()>
}
impl<'a> SummingWidgetRef<'a> {
pub fn new() -> Self {
Self{
sum_so_far: 0.0,
phantom: PhantomData,
}
}
}
impl<'a> DoesStuff for SummingWidgetRef<'a> {
type Output = &'a f64;
fn do_thing(&mut self, x: f64) -> Self::Output {
self.sum_so_far += x;
&self.sum_so_far
}
}
But even that is no good, because the compiler complains that:
method was supposed to return data with lifetime `'a` but it is returning data with lifetime `'1`
At which point I'm stuck. How can I tell the compiler that the return type should match the lifetime of the struct?
The original way to do this is to make the trait take self
by value, and then implement it for references.
trait DoesStuff {
type Output;
fn do_thing(self, x: f64) -> Self::Output;
}
pub struct SummingWidget {
sum_so_far: f64
}
impl<'a> DoesStuff for &'a mut SummingWidget {
type Output = &'a f64;
fn do_thing(self, x: f64) -> Self::Output {
self.sum_so_far += x;
&self.sum_so_far
}
}
This works in most cases, and it's how several standard library implementations work, like impl<'a, T> IntoIterator for &'a [T]
.
The new option is to use generic associated types (GAT) (from eggyal's comment).
trait DoesStuff {
type Output<'a> where Self: 'a;
fn do_thing(&mut self, x: f64) -> Self::Output<'_>;
}
pub struct SummingWidget {
sum_so_far: f64
}
impl DoesStuff for SummingWidget {
type Output<'a> = &'a f64;
fn do_thing(&mut self, x: f64) -> Self::Output<'_> {
self.sum_so_far += x;
&self.sum_so_far
}
}
I believe the original method is slightly more flexible than the GAT method, since you can choose for do_thing
to take any type, whereas the GAT method must take &mut
. But if you have something more complex than the simple function from your example, you may need to use the GAT method.