Correct trait bounds to work with f64 and f32 and for Unsigned integer types

Hello,

I need my code to work with floating point numbers f32 and f64 and unsigned integer types u8, u32 etc. To be specific, I have defined the following super-traits :

use num;

pub trait FloatingPointNumber: num::Float + num::FromPrimitive {}

pub trait UnsignedIntegerNumber: num::Integer + num::Unsigned + Copy + num::FromPrimitive {}

I use these these super-traits to define a new trait like this :

pub trait BoundingBox2D<T1: types::FloatingPointNumber, T2: types::UnsignedIntegerNumber> {
    fn height(&self) -> T1;
    fn width(&self) -> T1;
    fn aspect_ratio(&self) -> T1 {
        return (self.height() / self.width()) as T1;
    }
    fn area(&self) -> T1 {
        return self.height() * self.width() as T1;
    }
    fn center(&self) -> (T1, T1);
    fn confidence(&self) -> T1;
    fn label(&self) -> T2;
    fn is_normalized(&self) -> bool;
}

And I want to implement this trait for a generic struct which is defined below :

pub struct NormalizedTFBoundingBox2D<
    T1: types::FloatingPointNumber,
    T2: types::UnsignedIntegerNumber,
> {
    ymin_: T1,
    xmin_: T1,
    ymax_: T1,
    xmax_: T1,
    conf_: T1,
    label_: T2,
}

The implementation over this struct is as follows ( sorry for verbosity but I want to understand the underlying reason for the error and I am including the implementation just for completeness ) :

impl<T1: types::FloatingPointNumber, T2: types::UnsignedIntegerNumber> BoundingBox2D<T1, T2>
    for NormalizedTFBoundingBox2D<T1, T2>
{
    fn height(&self) -> T1 {
        return self.ymax_ - self.ymin_;
    }

    fn width(&self) -> T1 {
        return self.xmax_ - self.xmin_;
    }

    fn center(&self) -> (T1, T1) {
        let center_y = self.ymin_ + (self.ymax_ - self.ymin_) / T1::from_f64(2f64).unwrap();
        let center_x = self.xmin_ + (self.xmax_ - self.xmin_) / T1::from_f64(2f64).unwrap();
        return (center_y, center_x);
    }

    fn confidence(&self) -> T1 {
        return self.conf_;
    }

    fn label(&self) -> T2 {
        return self.label_;
    }

    fn is_normalized(&self) -> bool {
        return true;
    }
}

impl<T1: types::FloatingPointNumber, T2: types::UnsignedIntegerNumber>
    NormalizedTFBoundingBox2D<T1, T2>
{
    pub fn new(ymin: T1, xmin: T1, ymax: T1, xmax: T1, confidence: T1, cls_label: T2) -> Self {
        return NormalizedTFBoundingBox2D {
            ymin_: ymin,
            xmin_: xmin,
            ymax_: ymax,
            xmax_: xmax,
            conf_: confidence,
            label_: cls_label,
        };
    }
}

When I run the following test I get an error :

pub mod bounding_box;

#[cfg(test)]
mod tests {
    use super::*;

   
    #[test]
    fn it_works2() {
        let bbox = bounding_box::NormalizedTFBoundingBox2D::<f64, u8>::new(
            0.0, 0.0, 0.1, 0.l, 0.9, 2u8,
        );
        assert_eq!(bbox.height(), 0.1f64);
    }
}

The error I get is :

error[E0599]: the function or associated item `new` exists for struct `NormalizedTFBoundingBox2D<f64, u8>`, but its trait bounds were not satisfied
  --> examiner_objdetect/src/lib.rs:18:72
   |
18 |           let bbox = bounding_box::NormalizedTFBoundingBox2D::<f64, u8>::new(
   |                                                                          ^^^ function or associated item cannot be called on `NormalizedTFBoundingBox2D<f64, u8>` due to unsatisfied trait bounds
   |
  ::: examiner_objdetect/src/bounding_box.rs:17:1
   |
17 | / pub struct NormalizedTFBoundingBox2D<
18 | |     T1: types::FloatingPointNumber,
19 | |     T2: types::UnsignedIntegerNumber,
20 | | > {
   | |_- function or associated item `new` not found for this struct
   |
note: the following trait bounds were not satisfied:
      `f64: FloatingPointNumber`
      `u8: UnsignedIntegerNumber`
  --> examiner_objdetect/src/bounding_box.rs:59:10
   |
59 | impl<T1: types::FloatingPointNumber, T2: types::UnsignedIntegerNumber>
   |          ^^^^^^^^^^^^^^^^^^^^^^^^^^      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ unsatisfied trait bound introduced here
   |          |
   |          unsatisfied trait bound introduced here
60 |     NormalizedTFBoundingBox2D<T1, T2>
   |     ---------------------------------

error[E0277]: the trait bound `f64: FloatingPointNumber` is not satisfied
  --> examiner_objdetect/src/lib.rs:18:20
   |
18 |         let bbox = bounding_box::NormalizedTFBoundingBox2D::<f64, u8>::new(
   |                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `FloatingPointNumber` is not implemented for `f64`
   |
note: required by a bound in `NormalizedTFBoundingBox2D`
  --> examiner_objdetect/src/bounding_box.rs:18:9
   |
17 | pub struct NormalizedTFBoundingBox2D<
   |            ------------------------- required by a bound in this
18 |     T1: types::FloatingPointNumber,
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `NormalizedTFBoundingBox2D`

error[E0277]: the trait bound `u8: UnsignedIntegerNumber` is not satisfied
  --> examiner_objdetect/src/lib.rs:18:20
   |
18 |         let bbox = bounding_box::NormalizedTFBoundingBox2D::<f64, u8>::new(
   |                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `UnsignedIntegerNumber` is not implemented for `u8`
   |
note: required by a bound in `NormalizedTFBoundingBox2D`
  --> examiner_objdetect/src/bounding_box.rs:19:9
   |
17 | pub struct NormalizedTFBoundingBox2D<
   |            ------------------------- required by a bound in this
18 |     T1: types::FloatingPointNumber,
19 |     T2: types::UnsignedIntegerNumber,
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `NormalizedTFBoundingBox2D`

I have checked the num crate and all the trait bounds specified are implemented for f64, f32 (for FloatingPointNumber ) and for all unsigned types ( for UnsignedIntegerNumber ). What is the source of this error and how can I correct it ? Are there pitfalls in specifying trait bounds that I should re member ?

It looks like you haven't implemented your own traits for f64 and u8.

Merely stating supertrait bounds doesn't implement your trait for every eligible type, if that's what you were thinking. If this happened, the implicitness would be surprising in a bad way – usually, one does not want all traits to be implemented for all types by default.

Also, it would be impossible in general – what if the trait had methods? You have methodless marker traits, but most traits need to have methods to be useful.

1 Like

For example, you may want:

pub trait FloatingPointNumber: num::Float + num::FromPrimitive {}

impl<T: num::Float + num::FromPrimitive> FloatingPointNumber for T {}

Or you might want only the specific types:

impl FloatingPointNumber for f32 {}
impl FloatingPointNumber for f64 {}
1 Like

Through @H2CO3 I got the reasoning perfectly. In the first part of the snippet you have posted : Am I correct in thinking that it is essentially making num::Float + num::FromPrimitive an alias for num::FloatingPointNumber ? So, the generic structure of the code allows for any fancy type as long as num::Float and num::FromPrimitive are implemented for it.

Comparatively, I think that the second snippet keeps the focus of the code to the intention of allowing for specific float types. That way a future programmer would have to explicitly specify their intention of extending the support to a newer type.

Ahh... I understand. I am a newbie to Rust and want to learn intently about it. This sort of explanation helps a lot in putting the language features firmly to my head. Thanks a lot.

No, they are not aliases, that's the point. If this were the case, then it would work as you initially expected. The supertrait relationship is asymmetric. The subtrait implies the declared supertrait bounds, but the supertrait bounds do not imply either a bound or an impl involving the subtrait.

They're not literally aliases, but it's not totally wrong to think of it like that in effect when there's a blanket impl -- the trait has supertrait requirements, and everything that meets those requirements is covered by the impl, so they are identical sets of types.

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.