Search code examples
rustreferencetraitslifetimeassociated-types

Lifetimes for implementing a trait's associated type with a reference


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?


Solution

  • 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.