Search code examples
rustlifetimetype-parameter

Communicating Rust Lifetimes for Type Parameters


I'm working on a simple complex number example, and trying to implement ref-value/value-ref operations as follows:

use std::ops::*;

#[derive(Clone, PartialEq)]
pub struct Complex<T: Sized + Clone> {
    pub re: T,
    pub im: T,
}

// Ref-Ref Multiplication
impl<'a, 'b, T: Sized + Clone> Mul<&'b Complex<T>> for &'a Complex<T>
where
    T: Add<T, Output = T>,
    T: Sub<T, Output = T>,
    &'a T: Add<&'b T, Output = T>,
    &'a T: Mul<&'b T, Output = T>,
    &'a T: Sub<&'b T, Output = T>,
{
    type Output = Complex<T>;
    fn mul(self, rhs: &'b Complex<T>) -> Complex<T> {
        panic!("// Details irrelevant")
    }
}

// Ref-Value Multiplication
impl<'a, 'b, T: Sized + Clone> Mul<Complex<T>> for &'a Complex<T>
where
    T: 'static,
    T: Add<T, Output = T>,
    T: Sub<T, Output = T>,
    &'a T: Add<&'b T, Output = T>,
    &'a T: Mul<&'b T, Output = T>,
    &'a T: Sub<&'b T, Output = T>,
{
    type Output = Complex<T>;
    fn mul(self, rhs: Complex<T>) -> Complex<T> {
        let t = &rhs;
        self.mul(t)
    }
}

The ref-ref implementation works, and from what I understand it it takes in two references of differing lifetimes, and returns a complex value-type. The ref-value part is where I'm having an issue; When I compile, the error is that rhs doesn't live long enough. I believe I know why this is already, and that is that T could hold a reference (either direct or indirectly) to rhs when the value is returned, thus rhs goes out of scope, but T could hold a reference to it still.

My question is how to communicate that T will not hold some reference to rhs in some shape or form.

Some notes on things that I've tried so far or looked at:

  1. Changed the lifetime specification on either Mul implementation.
  2. Tried lifetime-inheritence, but this specifieds a reference held by T will live at least as long as T, so I think I need something more in the lines of "at most."
  3. Looked at other implementations; Either does not implement the case, or just uses clone to bypass the issue.

Solution

  • As suggested by Peter Hall in the comments, the easiest solution is to derive Copy for your complex type, and implement the operations for values. For the ref-ref implementations and the ref-val implementations, you can then simply dereference the references and use the val-val implementation.

    If you want to make the approach you started work, you need higher-rank trait bounds:

    use std::ops::*;
    
    #[derive(Clone, PartialEq)]
    pub struct Complex<T: Clone> {
        pub re: T,
        pub im: T,
    }
    
    // Ref-Ref Multiplication
    impl<'a, 'b, T: Clone> Mul<&'b Complex<T>> for &'a Complex<T>
    where
        T: Add<T, Output = T>,
        T: Sub<T, Output = T>,
        &'a T: Add<&'b T, Output = T>,
        &'a T: Mul<&'b T, Output = T>,
        &'a T: Sub<&'b T, Output = T>,
    {
        type Output = Complex<T>;
        fn mul(self, rhs: &'b Complex<T>) -> Complex<T> {
            Complex {
                re: &self.re * &rhs.re - &self.im * &rhs.im,
                im: &self.re * &rhs.im + &self.im * &rhs.re,
            }
        }
    }
    
    // Ref-Value Multiplication
    impl<'a, T: Clone> Mul<Complex<T>> for &'a Complex<T>
    where
        T: Add<T, Output = T>,
        T: Sub<T, Output = T>,
        &'a T: for<'b> Add<&'b T, Output = T>,
        &'a T: for<'b> Mul<&'b T, Output = T>,
        &'a T: for<'b> Sub<&'b T, Output = T>,
    {
        type Output = Complex<T>;
        fn mul(self, rhs: Complex<T>) -> Complex<T> {
            let t = &rhs;
            self.mul(t)
        }
    }
    

    In your version, the lifetime 'b in the ref-value implementation is chosen by the user of the trait. Since the user could use any lifetime for 'b, rhs would need static lifetime for your code to be valid. What you want instead is that *'a T satisfies the given trait bounds for any given lifetime 'b, which is exactly what HRTBs are for.

    An alternative, less repetitive way of writing the trait bounds for the second implementation is this:

    impl<'a, T: Clone> Mul<Complex<T>> for &'a Complex<T>
    where
        Self: for<'b> Mul<&'b Complex<T>, Output = Complex<T>>,
    {
        type Output = Complex<T>;
        fn mul(self, rhs: Complex<T>) -> Complex<T> {
            self.mul(&rhs)
        }
    }