Dividing a float by a generic number

Hello! I've been learning Rust over the past few weeks, and have been having an issue with generics and traits.

I've been creating a simple Vector crate that mimics OpenGL's Vec2. I also wanted to have variants for f32, f64, usize, etc. To do this I split the functionality into a trait that can be implemented.

Unfortunately, I've been getting an error with my Vec2Traits.norm() method. The only solution to this that I've figured out is removing the method entirely. Here is my code (simplified into one file):

(Also I'm using the num_traits crate.)

use num_traits::{Num, real::Real};

trait Vec2Traits<T: Num + Real> {
    fn new(x: T, y: T) -> Self;

    // So default trait methods can access x and y
    fn x(&self) -> T;
    fn y(&self) -> T;

    fn mag(&self) -> T {
        self.mag2().sqrt()
    }

    fn mag2(&self) -> T {
        self.x() * self.x() + self.y() * self.y()
    }

    fn norm(&self) -> Self
    where
        // Not sure if this is idiomic
        Self: Sized,
    {
        // Main error here because {float} does not implement Div<T>
        let r = 1.0 / self.mag();
        Self::new(self.x() * r, self.y() * r)
    }
}

#[derive(Debug)]
struct Vec2 {
    pub x: f32,
    pub y: f32
}

impl Vec2Traits<f32> for Vec2 {
    fn new(x: f32, y: f32) -> Self {
        Vec2 {x, y}
    }

    fn x(&self) -> f32 {
        self.x
    }

    fn y(&self) -> f32 {
        self.y
    }
}

fn main() {
    let v = Vec2::new(1.0, 5.0);
}

The error I'm getting is the following:

error[E0277]: cannot divide `{float}` by `T`
  --> examples/vecslol.rs:22:21
   |
22 |         let r = 1.0 / self.mag();
   |                     ^ no implementation for `{float} / T`
   |
   = help: the trait `Div<T>` is not implemented for `{float}`

For more information about this error, try `rustc --explain E0277`.
error: could not compile `manyvecs` due to previous error
exit status 101

Thank you for helping out! This is my first post, so I'm sorry if it's a little scuffed. :smiley:

The problem is that the compiler makes sure the method works with any type T that fulfills the trait bounds, in this case it needs to implement Num and Real. I could then create my own type

struct My;
impl Num for My { ... }
impl Real for My { ... }
impl Vec2Traits<My> for ...

and now it's no longer so clear what

let r = 1.0 / self.mag();

should do: 1.0 / self.mag() is not a valid operation because a float cannot be divided by a My (there is no impl for Div<My> for floats). Here's one option

    fn norm(&self) -> Self
    where
        Self: Sized,
        T: num_traits::One + std::ops::Div<T>,
    {
        let r = T::one() / self.mag();
        Self::new(self.x() * r, self.y() * r)
    }

We add trait bounds to the method*, One lets us create something that corresponds to the number one for our arbitrary type T, and Div<T> lets us divide our type T with itself.

*You could also add them to the trait itself, the right answer depends on which one makes more sense to you. Does it make sense to use the trait with some type that does not fulfill these trait bounds and cannot call norm? If not, put them on the trait; if yes, put them on the method.

edit: I just discovered that the One bound is unnecessary: it is already implied by T: Num. So you can remove it and it still works fine

    fn norm(&self) -> Self
    where
        Self: Sized,
        T: Div<T>,
    {
        let r = T::one() / self.mag();
        Self::new(self.x() * r, self.y() * r)
    }

Thank you for the quick reply! It seems that T::one() creates the number 1 in a way that T can understand, is there a way to do this for some other non-1/0 number? (Like 52?)

// Maybe something like this?
let r = T::from(52) / self.mag();

You can change the bound to

T: Div<T> + From<f64>,

to use

<T as From<f64>>::from(1.0)

(the as From bit is needed because the compiler doesn't know if we're trying to use From::from or NumCast::from)

Thank you so much! This is incredibly helpful. :slight_smile:

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.