Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Binary operation cannot be applied when using generics for arithmetic

Tags:

generics

rust

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
like image 247
user124784 Avatar asked Jan 07 '23 01:01

user124784


1 Answers

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:

  • How to write a trait bound for adding two references of a generic type?
  • How do I write the lifetimes for references in a type constraint when one of them is a local reference?
  • How does for<> syntax differ from a regular lifetime bound?
like image 129
Shepmaster Avatar answered Jan 16 '23 15:01

Shepmaster