Lifetimes when shadowing Additon for references

I'm trying to implement a custom addition that defaults to normal for types that have implemented addition on their references but I cannot figure out how to write this without the compiler complaining:

struct A {}
trait SymbolicAdd<T> {
    type Output<'c>;
    fn add_sym<'a, 'b, 'c>(&'a self, other: &'b T, state: &'c A) -> Option<Self::Output<'c>>;
}

impl<'c, T: 'c> SymbolicAdd<T> for T
where
    &'c T: std::ops::Add<&'c T>,
{
    type Output<'e> = <&'c T as std::ops::Add>::Output;
    fn add_sym<'a, 'b, 'e>(&'a self, other: &'b T, _state: A) -> Option<Self::Output<'e>> {
        Some(self + other)
    }
}

(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
error[E0207]: the lifetime parameter `'c` is not constrained by the impl trait, self type, or predicates
 --> src/lib.rs:7:6
  |
7 | impl<'c, T: 'c> SymbolicAdd<T> for T
  |      ^^ unconstrained lifetime parameter

For more information about this error, try `rustc --explain E0207`.
error: could not compile `playground` (lib) due to previous error

Without references it works:

struct A {}
trait SymbolicAdd<T> {
    type Output;
    fn add_sym(self, other: T, state: A) -> Option<Self::Output>;
}

impl<T> SymbolicAdd<T> for T
where
    T: std::ops::Add<T>,
{
    type Output = T::Output;
    fn add_sym(self, other: T, _state: A) -> Option<Self::Output> {
        Some(self + other)
    }
}

(Playground)

Depends a bit what kind of dependence of the Output type on the lifetimes of the input references you want. If you want it independent of them, and just depend on the lifetime of the _state argument, then something like

struct A {}
trait SymbolicAdd<T> {
    type Output<'c>;
    fn add_sym<'a, 'b, 'c>(&'a self, other: &'b T, state: &'c A) -> Option<Self::Output<'c>>;
}

impl<T, Output> SymbolicAdd<T> for T
where
    for<'a, 'b> &'a T: std::ops::Add<&'b T, Output = Output>,
{
    type Output<'c> = Output;
    fn add_sym<'a, 'b, 'c>(&'a self, other: &'b T, _state: &'c A) -> Option<Self::Output<'c>> {
        Some(self + other)
    }
}

could work.

Otherwise, you might need more lifetime arguments to type Output, or restrict the parameter lifetimes of add_sym to be the same, or the like.

Types like i32 do implement &Self: Add<&Self> in such a way that the Output type (being i32 itself again) is independent of the input lifetimes, so with those that works.

The main thing of interest might be those other implementations, i.e. the ones not from this default implementation. If those work well with the precise trait definition you’ve given us, then that might be about it and your problem is solved, I guess.


The code I gave above can also be simplified by using lifetime elision. Also the HRTB can be weakened a bit by only requiring &T: Add<&T> implementations to exist for the case where both lifetimes are the same. Not a huge gain, as most implementors should support distinct lifetimes anyways, but why ask for more than absolutely necessary :slight_smile:

struct A {}
trait SymbolicAdd<T> {
    type Output<'c>;
    fn add_sym<'c>(&self, other: &T, state: &'c A) -> Option<Self::Output<'c>>;
}

impl<T, Output> SymbolicAdd<T> for T
where
    for<'a> &'a T: std::ops::Add<&'a T, Output = Output>,
{
    type Output<'c> = Output;
    fn add_sym<'c>(&self, other: &T, _state: &'c A) -> Option<Output> {
        Some(self + other)
    }
}
1 Like

In general an Add implementation isn't going to return a reference; when references implement Add it's generally like so

impl Add<&Thing> for &Thing {
    type Output = Thing; // or Other, but still lifetime ambivalent
    // ...
}

And not this

impl<'a> Add<&'a Thing> for &'a Thing {
    type Output = &'a Thing; // or &'a ContainedThing
}

So I suspect you really want to not propogate any lifetimes in the methods, and to require a single output type both in the trait and your implementation, like so:

struct A {}
trait SymbolicAdd<T> {
    type Output;
    fn add_sym(&self, other: &T, state: &A) -> Option<Self::Output>;
}

impl<T, Out> SymbolicAdd<T> for T
where
    for<'any> &'any T: Add<&'any T, Output = Out>,
{
    type Output = Out;
    fn add_sym(&self, other: &T, state: &A) -> Option<Self::Output> {
        Some(self + other)
    }
}

Yes that's exactly what I need! I'm quite new to rust and hadn't thought of using generics on the output type in that way! Thank you both for your help!