Multiple errors related to expected `base_type` found `<base_type as trait>::Output`

Hi! I'm making an implementation of a ray tracer, but I'm getting stuck on this one portion, the Ray struct. The errors I'm getting are this:

Hidden for space
error[E0277]: cannot multiply `Vec3<f64>` by `<f64 as Sub<T>>::Output`
  --> src/types/ray.rs:23:60
   |
23 | ...64>::new(1.0, 1.0, 1.0) * (1.0 - t.into()) + Vec3::<f64>::new(0.5, 0.7, 1.0) * t;
   |                            ^ no implementation for `Vec3<f64> * <f64 as Sub<T>>::Output`
   |
   = help: the trait `Mul<<f64 as Sub<T>>::Output>` is not implemented for `Vec3<f64>`
   = help: the following other types implement trait `Mul<Rhs>`:
             <Vec3<f64> as Mul<i8>>
             <Vec3<f64> as Mul<i16>>
             <Vec3<f64> as Mul<i32>>
             <Vec3<f64> as Mul<i64>>
             <Vec3<f64> as Mul<i128>>
             <Vec3<f64> as Mul<u8>>
             <Vec3<f64> as Mul<u16>>
             <Vec3<f64> as Mul<u32>>
           and 16 others

error[E0308]: mismatched types
  --> src/types/ray.rs:28:27
   |
28 |         let oc: Vec3<T> = self.origin - center;
   |                 -------   ^^^^^^^^^^^^^^^^^^^^ expected `Vec3<T>`, found associated type
   |                 |
   |                 expected due to this
   |
   = note:       expected struct `Vec3<T>`
           found associated type `<Vec3<T> as Sub>::Output`
   = help: consider constraining the associated type `<Vec3<T> as Sub>::Output` to `Vec3<T>`
   = note: for more information, visit https://doc.rust-lang.org/book/ch19-03-advanced-traits.html

error[E0308]: mismatched types
  --> src/types/ray.rs:31:22
   |
31 |         let c: f64 = oc.dot(&oc) - radius * radius;
   |                ---   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `f64`, found associated type
   |                |
   |                expected due to this
   |
   = note:         expected type `f64`
           found associated type `<f64 as Sub<T>>::Output`
   = help: consider constraining the associated type `<f64 as Sub<T>>::Output` to `f64`
   = note: for more information, visit https://doc.rust-lang.org/book/ch19-03-advanced-traits.html

error[E0277]: cannot multiply `{integer}` by `f64`
  --> src/types/ray.rs:32:43
   |
32 |         let discriminant: f64 = b * b - 4 * a * c;
   |                                           ^ no implementation for `{integer} * f64`
   |
   = help: the trait `Mul<f64>` is not implemented for `{integer}`
   = help: the following other types implement trait `Mul<Rhs>`:
             <isize as Mul>
             <isize as Mul<&isize>>
             <i8 as Mul>
             <i8 as Mul<&i8>>
             <i16 as Mul>
             <i16 as Mul<&i16>>
             <i32 as Mul>
             <i32 as Mul<&i32>>
           and 49 others

error[E0308]: mismatched types
  --> src/types/ray.rs:32:33
   |
32 |         let discriminant: f64 = b * b - 4 * a * c;
   |                           ---   ^^^^^^^^^^^^^^^^^ expected `f64`, found associated type
   |                           |
   |                           expected due to this
   |
   = note:         expected type `f64`
           found associated type `<f64 as Sub<T>>::Output`
   = help: consider constraining the associated type `<f64 as Sub<T>>::Output` to `f64`
   = note: for more information, visit https://doc.rust-lang.org/book/ch19-03-advanced-traits.html

Some errors have detailed explanations: E0277, E0308.
For more information about an error, try `rustc --explain E0277`.

Here's the ray.rs file:

Hidden for space
use std::ops::{Add, Div, Mul, Sub};

use super::Vec3;

#[derive(Debug, PartialEq, Eq, Copy, Clone, Default)]
pub struct Ray<T> {
    pub origin: Vec3<T>,
    pub direction: Vec3<T>
}

impl<T: Mul<Output = T> + Add<Output = T> + From<f64> + Into<f64> + Copy + Sub<Output = T>> Ray<T>
where Vec3<T>: Div<f64, Output = Vec3<T>> + Mul + Sub, f64: Sub<T> {
    pub fn new(origin: Vec3<T>, direction: Vec3<T>) -> Self {
        Self {
            origin,
            direction
        }
    }

    pub fn to_color(&self) -> Vec3<f64> {
        let unit_direction: Vec3<T> = self.direction.unit_vector();
        let t: f64 = ((unit_direction.y + 1.0.into()) * 0.5.into()).into();
        let g: Vec3<f64> = Vec3::<f64>::new(1.0, 1.0, 1.0) * (1.0 - t) + Vec3::<f64>::new(0.5, 0.7, 1.0) * t;
        g
    }

    pub fn hit_sphere(&self, center: Vec3<T>, radius: T) -> bool {
        let oc: Vec3<T> = self.origin - center;
        let a: f64 = self.direction.dot(&self.direction);
        let b: f64 = 2.0 * oc.dot(&self.direction);
        let c = oc.dot(&oc) - radius * radius;
        let discriminant = b * b - 4 * a * c;
        discriminant > 0
    }
}

And the vec.rs file:

Hidden for space
use std::{
    fmt::Display,
    ops::{
        Add, AddAssign, Div, DivAssign, Index, IndexMut, Mul, MulAssign, Neg, Not, Sub, SubAssign,
    },
};

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

macro_rules! impl_ops_vec3 {
    ($t: ty => $($u: ty),* $(,)?) => {
        $(
            // Operations on Vec3s
            impl Add<Vec3<$u>> for Vec3<$t> {
                type Output = Vec3<$t>;

                fn add(self, rhs: Vec3<$u>) -> Self::Output {
                    Vec3 {
                        x: self.x + rhs.x as $t,
                        y: self.y + rhs.y as $t,
                        z: self.z + rhs.z as $t,
                    }
                }
            }

            impl AddAssign<Vec3<$u>> for Vec3<$t> {
                fn add_assign(&mut self, rhs: Vec3<$u>) {
                    self.x += rhs.x as $t;
                    self.y += rhs.y as $t;
                    self.z += rhs.z as $t;
                }
            }

            impl Sub<Vec3<$u>> for Vec3<$t> {
                type Output = Vec3<$t>;

                fn sub(self, rhs: Vec3<$u>) -> Self::Output {
                    Vec3 {
                        x: self.x - rhs.x as $t,
                        y: self.y - rhs.y as $t,
                        z: self.z - rhs.z as $t,
                    }
                }
            }

            impl SubAssign<Vec3<$u>> for Vec3<$t> {
                fn sub_assign(&mut self, rhs: Vec3<$u>) {
                    self.x -= rhs.x as $t;
                    self.y -= rhs.y as $t;
                    self.z -= rhs.z as $t;
                }
            }

            impl Mul<Vec3<$u>> for Vec3<$t> {
                type Output = Vec3<$t>;

                fn mul(self, rhs: Vec3<$u>) -> Self::Output {
                    Vec3 {
                        x: self.x * rhs.x as $t,
                        y: self.y * rhs.y as $t,
                        z: self.z * rhs.z as $t,
                    }
                }
            }

            impl MulAssign<Vec3<$u>> for Vec3<$t> {
                fn mul_assign(&mut self, rhs: Vec3<$u>) {
                    self.x *= rhs.x as $t;
                    self.y *= rhs.y as $t;
                    self.z *= rhs.z as $t;
                }
            }

            impl Div<Vec3<$u>> for Vec3<$t> {
                type Output = Vec3<$t>;

                fn div(self, rhs: Vec3<$u>) -> Self::Output {
                    Vec3 {
                        x: self.x / rhs.x as $t,
                        y: self.y / rhs.y as $t,
                        z: self.z / rhs.z as $t,
                    }
                }
            }

            impl DivAssign<Vec3<$u>> for Vec3<$t> {
                fn div_assign(&mut self, rhs: Vec3<$u>) {
                    self.x /= rhs.x as $t;
                    self.y /= rhs.y as $t;
                    self.z /= rhs.z as $t;
                }
            }

            // Operations on primatives
            impl Mul<$u> for Vec3<$t> {
                type Output = Vec3<$t>;

                fn mul(self, rhs: $u) -> Self::Output {
                    Vec3 {
                        x: self.x * rhs as $t,
                        y: self.y * rhs as $t,
                        z: self.z * rhs as $t,
                    }
                }
            }

            impl MulAssign<$u> for Vec3<$t> {
                fn mul_assign(&mut self, rhs: $u) {
                    self.x *= rhs as $t;
                    self.y *= rhs as $t;
                    self.z *= rhs as $t;
                }
            }

            impl Div<$u> for Vec3<$t> {
                type Output = Vec3<$t>;

                fn div(self, rhs: $u) -> Self::Output {
                    Vec3 {
                        x: self.x / rhs as $t,
                        y: self.y / rhs as $t,
                        z: self.z / rhs as $t,
                    }
                }
            }

            impl DivAssign<$u> for Vec3<$t> {
                fn div_assign(&mut self, rhs: $u) {
                    let k = 1.0 / rhs as i32 as f64; // Forgive me lord, for I have sinned.
                    self.x *= k as $t;
                    self.y *= k as $t;
                    self.z *= k as $t;
                }
            }

        )*
    }
}

// Implement operators for integer types
impl_ops_vec3!(i8 => f32, f64, i8, i16, i32, i64, i128, u8, u16, u32, u64, u128, bool);
impl_ops_vec3!(i16 => f32, f64, i8, i16, i32, i64, i128, u8, u16, u32, u64, u128, bool);
impl_ops_vec3!(i32 => f32, f64, i8, i16, i32, i64, i128, u8, u16, u32, u64, u128, bool);
impl_ops_vec3!(i64 => f32, f64, i8, i16, i32, i64, i128, u8, u16, u32, u64, u128, bool);
impl_ops_vec3!(i128 => f32, f64, i8, i16, i32, i64, i128, u8, u16, u32, u64, u128, bool);

// Implement operators for float types
impl_ops_vec3!(f32 => f32, f64, i8, i16, i32, i64, i128, u8, u16, u32, u64, u128);
impl_ops_vec3!(f64 => f32, f64, i8, i16, i32, i64, i128, u8, u16, u32, u64, u128);

// Implement operators for unsigned types
impl_ops_vec3!(u8 => f32, f64, i8, i16, i32, i64, i128, u8, u16, u32, u64, u128, bool);
impl_ops_vec3!(u16 => f32, f64, i8, i16, i32, i64, i128, u8, u16, u32, u64, u128, bool);
impl_ops_vec3!(u32 => f32, f64, i8, i16, i32, i64, i128, u8, u16, u32, u64, u128, bool);
impl_ops_vec3!(u64 => f32, f64, i8, i16, i32, i64, i128, u8, u16, u32, u64, u128, bool);
impl_ops_vec3!(u128 => f32, f64, i8, i16, i32, i64, i128, u8, u16, u32, u64, u128, bool);

// Universal operators
impl<T: Neg> Neg for Vec3<T> {
    type Output = Vec3<<T as Neg>::Output>;

    fn neg(self) -> Self::Output {
        Vec3 {
            x: -self.x,
            y: -self.y,
            z: -self.z,
        }
    }
}

impl<T: Not> Not for Vec3<T> {
    type Output = Vec3<<T as Not>::Output>;

    fn not(self) -> Self::Output {
        Vec3 {
            x: !self.x,
            y: !self.y,
            z: !self.z,
        }
    }
}

impl<T> Index<i32> for Vec3<T> {
    type Output = T;
    fn index(&self, i: i32) -> &Self::Output {
        match i {
            -1 => &self.z,
            0 => &self.x,
            1 => &self.y,
            2 => &self.z,
            _ => panic!("len was 2, but index was {}", i),
        }
    }
}

impl<T> IndexMut<i32> for Vec3<T> {
    fn index_mut(&mut self, i: i32) -> &mut Self::Output {
        match i {
            -1 => &mut self.z,
            0 => &mut self.x,
            1 => &mut self.y,
            2 => &mut self.z,
            _ => panic!("len was 2, but index was {}", i),
        }
    }
}

impl<T: Display> Display for Vec3<T> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "({}, {}, {})", self.x, self.y, self.z)
    }
}

impl<T: Mul<Output = T> + Add<Output = T> + Into<f64> + Copy + Sub<Output = T>> Vec3<T>
where Vec3<T>: Div<f64, Output = Vec3<T>> {
    pub fn new(x: T, y: T, z: T) -> Self {
        Vec3 { x, y, z }
    }

    pub fn length(&self) -> f64 {
        (self.x * self.x + self.y * self.y + self.z * self.z)
            .into()
            .sqrt()
    }

    pub fn square_length(&self) -> f64 {
        (self.x * self.x + self.y * self.y + self.z * self.z).into()
    }

    pub fn dot(&self, other: &Vec3<T>) -> f64 {
        (self.x * other.x + self.y * other.y + self.z * other.z).into()
    }

    pub fn cross(&self, other: &Vec3<T>) -> Self {
        Self {
            x: self.y * other.z - self.z * other.y,
            y: self.x * other.z - self.z * other.x,
            z: self.x * other.y - self.y * other.x,
        }
    }

    pub fn unit_vector(self) -> Self {
        self / self.length()
    }
}

What am I doing wrong? It doesn't seem like I've specified any of these types anywhere. How do I further constrain the types? The docs page doesn't seem to have anything. Any help is appreciated, thanks!

In Rust Mul doesn't have to multiply the same types, and the result doesn't have to be any of the types multiplied, so result of T * T is not guaranteed to be T. It's not guaranteed that the type T is even an number. There could be a Mul implementation that can multiply Florp by [Blep; 123] and returns a Vec<Blah>.

You need to forbid such weirdness in your generic bounds T: Mul<T, Output = T>.

However, generic numeric code is painful in Rust. Generics are not like in C++ where you can write whatever you want, and it's OK as long as it compiles with the types supplied. Generics in Rust are checked at definition time, and they have no idea you just want to work with numbers. Generics must be valid for all types allowed by their type bounds, so at every step the compiler will bug you "but what if x was an array of strings!? how do you multiply that!?", and you will have to make your generic code compatible with all hypothetical scenarios, even ones that make no sense whatsoever.

I suggest in order of preference:

  1. Make the implementation use just one specific type like f64. You can still have it configurable at compile time with type MyFloat = f64;

  2. If you need to support both f32 and f64 at the same time, define your functions via macro_rules, creating copies of the functions for each type.

1 Like

I do just have the macro_rules, generic T, and other things for type compatibility. Do I need it? Should I just implement f64 and i32? If so, are there any other types I should probably implement?

In your case it'd be easier to use macro_rules to implement Ray<T> not on any T, but on Ray<f32> and Ray<f64> and other concrete types you need.

Alternatively, you can simplify the trait bounds with num_traits. It has traits like Float and AsPrimitive that give a lot of functionality that overly broad libstd trait bounds don't:


If you just need Vec3, there's:

I did see glam before, but I wanted to take a crack at making my own implementation, but it turns out that's harder than I expected, and I don't need much customizability with this, so I'll probably just use it.

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.