5

I'm trying to implement generics within my library using the num crate Float trait, but I'm stuck fighting the compiler. This works:

struct Vector<T> {
    data: Vec<T>,
}

trait Metric<T> {
    fn norm(&self) -> T;
}

impl Metric<f32> for Vector<f32> {
    fn norm(&self) -> f32 {
        let mut s = 0.0;

        for u in &self.data {
            s = s + u * u;
        }

        s.sqrt()
    }
}

But this doesn't:

use num::Float; // 0.2.0

struct Vector<T> {
    data: Vec<T>,
}

trait Metric<T> {
    fn norm(&self) -> T;
}

impl<T: Float> Metric<T> for Vector<T> {
    fn norm(&self) -> T {
        let mut s = T::zero();

        for u in &self.data {
            s = s + u * u;
        }

        s.sqrt()
    }
}

The latter gives me the following error:

error[E0369]: binary operation `*` cannot be applied to type `&T`
  --> src/lib.rs:16:23
   |
16 |             s = s + u * u;
   |                     - ^ - &T
   |                     |
   |                     &T
   |
   = note: an implementation of `std::ops::Mul` might be missing for `&T`

If I remove the reference and iterate over self.data instead, I get a

error[E0507]: cannot move out of borrowed content
  --> src/lib.rs:15:18
   |
15 |         for u in self.data {
   |                  ^^^^^^^^^ cannot move out of borrowed content
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
user124784
  • 896
  • 1
  • 13
  • 22

1 Answers1

10

Let's look closer at the Float trait. It is defined as:

pub trait Float: NumCast + Num + Copy + Neg<Output = Self> + PartialOrd<Self> {
    // ...
}

Diving into the Num trait, we see:

pub trait Num: Zero + One + NumOps<Self, Self> + PartialEq<Self> {
    // ...
}

And deeper into NumOps

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>
{
    // ...
}

That means that any type that implements Float is able to be multiplied by its own type. Now let's turn back to your code. You are iterating over a Vec<T>, which gives you a reference to each item, a &T.

You have a &T and are trying to multiply that by another &T. Here's a simplified example of that:

fn do_a_thing<T>(a: &T, b: &T)
where
    T: Float,
{
    let z = a * b;
}

This gives the same error: binary operation `*` cannot be applied to type `&T`.

The problem is that you only know that you can multiply a T by another T. To say that, you have to explicitly dereference the variables. Since Float also requires Copy, this will work:

let z = (*a) * (*b);

Applying the same change to your original code causes it to work:

for u in &self.data {
    s = s + (*u) * (*u);
}

You can also dereference the iterator variable when pattern matching:

for &u in &self.data {
    s = s + u * u;
}

Or you can add another bound that requires that references to your type can be multiplied:

impl<T> Metric<T> for Vector<T>
where
    T: Float,
    for<'a> &'a T: std::ops::Mul<&'a T, Output = T>,
{
    fn norm(&self) -> T {
        let mut s = T::zero();

        for u in &self.data {
            s = s + u * u;
        }

        s.sqrt()
    }
}

You can also add in a bound for AddAssign and write simpler code in the body:

impl<T> Metric<T> for Vector<T>
where
    T: Float + std::ops::AddAssign,
    for<'a> &'a T: std::ops::Mul<&'a T, Output = T>,
{
    fn norm(&self) -> T {
        let mut s = T::zero();

        for u in &self.data {
            s += u * u;
        }

        s.sqrt()
    }
}

See also:

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • Thanks for clearing that up - it's really helpful to see why this is the case. As for the += part am I correct in assuming that it is just not possible to use this operator for generics? – user124784 Dec 08 '15 at 17:09
  • 1
    @user124784 [not yet](https://doc.rust-lang.org/nightly/std/ops/trait.AddAssign.html), but it's coming. [Specifying good semantics](https://github.com/rust-lang/rfcs/pull/953) for that operator is tougher than you'd expect :-) – Shepmaster Dec 08 '15 at 17:10
  • I can wait :). Just to check though - the compiler's optimization will not see any difference between these? – user124784 Dec 09 '15 at 02:29