Beginner Query: implementing traits for generic structs


#1

Okay. I like the idea of rust, but I’ve not found the experience of learning rust to be especially user-friendly. I’m working on a computer vision application, and I’d like to build a simple math library to get myself started. My first crack at the implementation is starting with the lowly 3D vector.

struct Vec3<T : Add + Copy> {
    x: T,
    y: T,
    z: T
}

My thought is that any type that should live in the vector struct needs to support addition. The next step is to define addition on my Vec3, like so:

impl<T> Add for Vec3<T> where T:Add + Copy {
    type Output = Vec3<T>;

    fn add(self, rhs: Vec3<T>) -> Vec3<T> {
    	Vec3<T:Add+Copy>{x: (self.x + rhs.x), y: (self.y + rhs.y), z: (self.z + rhs.z)}
    }
}

This doesn’t work! rustc tells me that the problem is as follows:

error: expected one of `!`, `.`, `::`, `;`, `{`, `}`, or an operator, found `:`
geometry/three/mod.rs:13     	Point<T:Add+Copy>{x: (self.x + rhs.x), y: (self.y + rhs.y), z: (self.z + rhs.z)}

It’s quite easy to modify things to get other errors… but it’s not clear to me how this example should be written to make the compiler happy. I would appreciate the wisdom of those who have trod this path before me.


#2

You don’t specify type parameters when constructing a struct:

Vec3 {
    x: (self.x + rhs.x),
    y: (self.y + rhs.y),
    z: (self.z + rhs.z),
}

#3

I appreciate the tip, but removing the type parameters just leads me to new errors. This time of the general form:

error: mismatched types:
 expected `Vec3<T>`,
    found `Vec3<<T as core::ops::Add>::Output>`
(expected type parameter,
    found associated type) [E0308]
Vec3{x: (self.x + rhs.x), y: (self.y + rhs.y), z: (self.z + rhs.z)}

error: the trait `core::marker::Copy` is not implemented for the type `<T as core::ops::Add>::Output` [E0277]
error: the trait `core::ops::Add` is not implemented for the type `<T as core::ops::Add>::Output` [E0277]

I’m assuming the main problem is the first one, that instead of “T”, the compiler found “T as core::ops::Add>::Output”

It’s not clear to me why this is happening or what I’m supposed to be doing differently.


#4

Fundamentally, the issue is that the output type of addition isn’t directly connected to the type of the operands; addition is allowed to return any type that makes sense (for example, you could say that adding a real number and an imaginary number returns a complex number).

There are a couple of different ways you can go; make the implementation a bit more flexible:

impl<T> Add for Vec3<T> where T:Add + Copy, T::Output:Add+Copy {
    type Output = Vec3<T::Output>;

    fn add(self, rhs: Vec3<T>) -> Vec3<T::Output> {
        Vec3 {
            x: (self.x + rhs.x),
            y: (self.y + rhs.y),
            z: (self.z + rhs.z),
        }
    }
}

or lock it down a bit more:

impl<T> Add for Vec3<T> where T:Add<Output=T> + Copy {
    type Output = Vec3<T>;

    fn add(self, rhs: Vec3<T>) -> Vec3<T> {
        Vec3 {
            x: (self.x + rhs.x),
            y: (self.y + rhs.y),
            z: (self.z + rhs.z),
        }
    }
}

#5

Thank you very much! This is quite helpful. I can’t say that I completely understand the syntax/semantics here, however.

To wit, let’s say I’d like to define my geometry so that I have points and vectors, defined as their own structs, but in reality they have the same layout:

struct Vec3<T : Add + Copy> {
    x: T,
    y: T,
    z: T
}

struct Pnt3<T : Add + Copy> {
    x: T,
    y: T,
    z: T
}

I’d now like to define addition between two vectors, and between a point and a vector, as follows:

Point = Vector + Point
Point = Point + Vector
Vector = Vector + Vector
plus scalar multiplication for vectors, etc.

In my naiveté, I would assume that I can do something like the following, and simply add another impl block:

impl<T> Add for Vec3<T> where T:Add<Output=T> + Copy {
    type Output = Pnt3<T>;

    fn add(self, rhs: Pnt3<T>) -> Pnt3<T> {
        Pnt3 {
            x: (self.x + rhs.x),
            y: (self.y + rhs.y),
            z: (self.z + rhs.z),
        }
    }
}

In my mind this is saying: define addition between the point and the vector, where each is parameterized over the same type, and return a new point. The compiler does not agree with my mental model and complains thusly:

error: conflicting implementations for trait `core::ops::Add` [E0119]

So I can see why they would conflict… but I’m pretty sure this is possible, since what I intend ought not to conflict. A vector plus a point is not the same as a vector plus a vector. So what is the correct syntax here?

I apologize again for my ignorance.


#6

Recall the Add trait definition:

pub trait Add<RHS = Self>

When you write just

impl<T> Add for Vec3<T>

you’re implementing Add<Self> and Self is Vec3<T> – “Something = Vector + Vector”.

The “Vector + Point” case would be Add<Pnt3<T>>.


#7

Okay, that actually makes perfect sense.

Hopefully I’ll make it further along now before needing to ask another simple question.

Thanks again!


#8

Okay, I’ve considered making another topic for this, but it does fit the overarching theme here…
Is there any way to group a set of traits together in a clean fashion? Something along the lines of:

trait VecNum: Add + Mul + Sub + Neg + Copy{

}

struct Vec3<T : VecNum> {
    x: T,
    y: T,
    z: T
}

This would avoid having a preponderance of trait bounds on my vector parameter for each trait I implement on the vector/points.


#9

The non-stdlib num crate has such a trait: http://doc.rust-lang.org/num/num/traits/trait.Num.html