2
votes

My question is similar to the one addressed in this issue.

I'm trying to make a generic vector struct and I have the following working:

use std::ops::{Add, Sub};

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Vec2<T> where 
    T: Add<Output = T> + Sub<Output = T>
{
    pub x: T,
    pub y: T,
}

impl<T> Vec2<T> where
    T: Add<Output = T>  + Sub<Output = T>
{
    pub fn new(x: T, y: T) -> Vec2<T> {
        Vec2 { x, y }
    }

}

// Overload `+` operator for Vec2
impl<T> Add for Vec2<T> where
    T: Add<Output = T>  + Sub<Output = T>
{
    type Output = Self;

    fn add(self, other: Self) -> Self::Output {
        Self {
            x: self.x + other.x,
            y: self.y + other.y,
        }
    }
}

// Overload `-` operator for Vec2
impl<T> Sub for Vec2<T> where
    T: Add<Output = T>  + Sub<Output = T>
{
    type Output = Self;

    fn sub(self, other: Self) -> Self::Output {
        Self {
            x: self.x - other.x,
            y: self.y - other.y,
        }
    }
}

But as you can see, this Add<Output = T> + Sub<Output = T> bound is a little messy, especially if I plan to implement more traits. Is there some way to use macros or type aliasing to so I can do something along the lines of:

trait Num: Add + Sub {}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Vec2<T> where 
    T: Num
{
    ...
}

// Overload `+` operator for Vec2
impl<T> Add for Vec2<T> where
    T: Num
{
    ...
}

Note: Understandably, the code above produces a compilation error. If you look at the documentation for either the std::ops::Add or std::ops::Sub traits, they have a default generic type <Rhs = Self> whose size cannot be determined at compilation time, so I'm not sure if what I'm asking is even possible. But it would be nice if there was some workaround.

1

1 Answers

2
votes

The trait you are looking for can be found in num-traits crate:

pub trait NumOps<Rhs = Self, Output = Self>:
    Add<Rhs, Output = Output>
    + Sub<Rhs, Output = Output>
    + Mul<Rhs, Output = Output>
    + Div<Rhs, Output = Output>
    + Rem<Rhs, Output = Output>
{
}

impl<T, Rhs, Output> NumOps<Rhs, Output> for T where
    T: Add<Rhs, Output = Output>
        + Sub<Rhs, Output = Output>
        + Mul<Rhs, Output = Output>
        + Div<Rhs, Output = Output>
        + Rem<Rhs, Output = Output>
{
}

You can easily use it in your vector type:

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Vec2<T: NumOps> {
    pub x: T,
    pub y: T,
}

impl<T: NumOps> Add for Vec2<T> {
    type Output = Self;

    fn add(self, other: Self) -> Self::Output {
        Self {
            x: self.x + other.x,
            y: self.y + other.y,
        }
    }
}

Full code in playground.

But actually it is a better practice to narrow down every trait bound to the very minimum:

// No trait bound on T: Vec2 can store any type
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Vec2<T> {
    pub x: T,
    pub y: T,
}

impl<T> Vec2<T> {
    pub fn new(x: T, y: T) -> Vec2<T> {
        Vec2 { x, y }
    }

}

// Implement `+` operator for Vec2<T> only if T has it
impl<T> Add for Vec2<T>
    where T: Add<T, Output = T>
{
    type Output = Self;

    fn add(self, other: Self) -> Self::Output {
        Self {
            x: self.x + other.x,
            y: self.y + other.y,
        }
    }
}

// Implement `-` operator for Vec2<T> only if T has it
impl<T> Sub for Vec2<T>
    where T: Sub<T, Output = T>
{
    type Output = Self;

    fn sub(self, other: Self) -> Self::Output {
        Self {
            x: self.x - other.x,
            y: self.y - other.y,
        }
    }
}

Full code in playground.

This way, Vec2 can be constructed with any possible type, but Add is implemented only if T has corresponding Add; same for Sub.