Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mismatched types when returning the result of adding two generics

Tags:

rust

I am learning Rust, have read the Rust homepage, and am trying out small example programs. Here is code that fails:

use std::ops::Add;

pub struct Complex<T> {
    pub re: T,
    pub im: T,
}

impl <T: Add> Add<Complex<T>> for Complex<T> {
    type Output = Complex<T>;
    fn add(self, other: Complex<T>) -> Complex<T> {
        Complex {re: self.re + other.re, im: self.im + other.im}
    }
}

Here is the error message:

src/lib.rs:11:3: 11:59 error: mismatched types:
 expected `Complex<T>`,
    found `Complex<<T as core::ops::Add>::Output>`
(expected type parameter,
    found associated type) [E0308]
src/lib.rs:11       Complex {re: self.re + other.re, im: self.im + other.im}
                    ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

I don't understand why it fails to compile.

like image 975
Thorum357 Avatar asked Jun 05 '16 22:06

Thorum357


2 Answers

The Add trait is defined as

pub trait Add<RHS = Self> {
    type Output;
    fn add(self, rhs: RHS) -> Self::Output;
}

That is, given a type for Self (the type the trait is implemented for), and a type for the right-hand-side (RHS, the thing being added), there will be a unique type that is produced: Output.

Conceptually, this allows you to create a type A that can have a type B added to it, which will always produce a third type C.

In your example, you have constrained T to implement Add. By default, the RHS type is assumed to be the same as the type the trait is implemented for (RHS = Self). However, there are no restrictions placed on what the output type must be.

There are two potential solutions:

  1. Say that you will return a Complex that has been parameterized by whatever the result type of adding T is:

    impl<T> Add<Complex<T>> for Complex<T>
    where
        T: Add,
    {
        type Output = Complex<T::Output>;
    
        fn add(self, other: Complex<T>) -> Complex<T::Output> {
            Complex {
                re: self.re + other.re,
                im: self.im + other.im,
            }
        }
    }
    
  2. Restrict T to those types that when added to themselves return the same type:

    impl<T> Add<Complex<T>> for Complex<T>
    where
        T: Add<Output = T>,
    {
        type Output = Complex<T>;
    
        fn add(self, other: Complex<T>) -> Complex<T> {
            Complex {
                re: self.re + other.re,
                im: self.im + other.im,
            }
        }
    }
    

See also:

  • How do I require a generic type implement an operation like Add, Sub, Mul, or Div in a generic function?
  • How to write a trait bound for adding two references of a generic type?
like image 52
Shepmaster Avatar answered Nov 21 '22 00:11

Shepmaster


Your implementation of add produces a Complex<<T as core::ops::Add>::Output>. <T as core::ops::Add>::Output (i.e. the Output of the implementation of Add<T> for T) is not guaranteed to be the same as T. You can add a constraint on the Output associated type to restrict your implementation to be only available when they are in fact the same:

impl<T: Add<Output = T>> Add for Complex<T> {
    type Output = Complex<T>;

    fn add(self, other: Complex<T>) -> Complex<T> {
        Complex { re: self.re + other.re, im: self.im + other.im }
    }
}

Or, you could change your implementation to be as generic as possible by making it possible to add a Complex<T> and a Complex<U>, provided that it is possible to add a T and a U, and by returning a Complex<<T as Add<U>>::Output>.

impl<T: Add<U>, U> Add<Complex<U>> for Complex<T> {
    type Output = Complex<<T as Add<U>>::Output>;

    fn add(self, other: Complex<U>) -> Self::Output {
        Complex { re: self.re + other.re, im: self.im + other.im }
    }
}
like image 4
Francis Gagné Avatar answered Nov 20 '22 22:11

Francis Gagné