genericsrustpolymorphismtraitsparametric-polymorphism

How to constrain type of right hand side parameter of operator implementation?


Trying to implement a generic Vec implementing the std::ops::Add trait. I want the implementation to automatically convert the underlying type of the vector on addition so I can do something like this:

let u: Vec3<i32> = Vec3 { x: 1, y: 2, z: 3 };
let v: Vec3<i8> = Vec3 { x: 2, y: 3, z: 4 };
let w = u + v;

without using .into() at call site. I have implemented the From trait (which automatically add the Into implementation) and it's working fine on a simple function:

fn foo<T: Into<Vec3<i32>>>(input: T) -> Vec3<i32> {
    input.into()
}

let v: Vec3<i8> = Vec3 { x: 2, y: 3, z: 4 };
println!("{:?}", foo(v));

Link to the non-compiling example here. Is it possible to express a constraint on the type of the right hand side argument of an operator and if yes, how?

#![allow(dead_code)]

use std::convert::{From};
use std::ops::{Add};

#[derive(Debug)]
struct Vec3<T> {
    x: T,
    y: T,
    z: T,
}

impl<T: Add<Output=T>> Add<U: Into<Vec3<i32>>> for Vec3<T> {
    type Output = Vec3<T>;
    fn add(self, rhs: U) -> Self::Output {
        let rhs_converted: Vec3<T> = rhs.into();
        Vec3 {
            x: self.x.add(rhs_converted.x),
            y: self.y.add(rhs_converted.y),
            z: self.z.add(rhs_converted.z),
        }
    }
}

impl From<Vec3<i8>> for Vec3<i32> {
    fn from(input: Vec3<i8>) -> Self {
        Vec3 {
            x: input.x.into(),
            y: input.y.into(),
            z: input.z.into(),
        }
    }
}

fn foo<T: Into<Vec3<i32>>>(input: T) -> Vec3<i32> {
    input.into()
}

fn main() {
    let u: Vec3<i32> = Vec3 { x: 1, y: 2, z: 3 };
    let v: Vec3<i8> = Vec3 { x: 2, y: 3, z: 4 };
    let w = u + v;
    
    // println!("{:?}", foo(v));
    println!("{:?}", w);
}

Solution

  • You need to change U: Into<Vec3<i32>> to U: Into<Vec3<T>> in your Add implementation. Also, the type parameter U declaration was missing from the generic impl, it should be impl<T, U> not just impl<T>. After these minor fixes your code works as expected:

    use std::convert::{From};
    use std::ops::{Add};
    
    #[derive(Debug)]
    struct Vec3<T> {
        x: T,
        y: T,
        z: T,
    }
    
    impl From<Vec3<i8>> for Vec3<i32> {
        fn from(input: Vec3<i8>) -> Self {
            Vec3 {
                x: input.x.into(),
                y: input.y.into(),
                z: input.z.into(),
            }
        }
    }
    
    impl<T, U> Add<U> for Vec3<T>
        where T: Add<Output=T>, U: Into<Vec3<T>>
    {
        type Output = Vec3<T>;
    
        fn add(self, rhs: U) -> Self::Output {
            let rhs_converted: Vec3<T> = rhs.into();
            Vec3 {
                x: self.x.add(rhs_converted.x),
                y: self.y.add(rhs_converted.y),
                z: self.z.add(rhs_converted.z),
            }
        }
    }
    
    fn main() {
        let u: Vec3<i32> = Vec3 { x: 1, y: 2, z: 3 };
        let v: Vec3<i8> = Vec3 { x: 2, y: 3, z: 4 };
        let w = u + v;
        println!("{:?}", w); // prints "Vec3 { x: 3, y: 5, z: 7 }" as expected
    }
    

    playground