Search code examples
rustfloating-pointtraitsintervals

Rust Inari crate - Using a generic function with Float as a trait when the type doesn't implement the Float trait


I am quite new to Rust, so sorry by advance if I'm misunderstanding something.

I have some code in Rust where I have generic functions such as this one :

pub(crate) fn square_root<T: Float>(value_array: &[T]) -> T {
    value_array[0].sqrt()
}

I also have this macro :

macro_rules! print_queries {
    ($INTERVAL:ty, $INTERVAL_INIT:expr, $TYPE_NAME:expr, $METHOD:expr, $PRINT_METHOD:expr, $VARIABLE_COUNT:expr) => {
        {
            let mut used_variables: Vec<$INTERVAL> = Vec::with_capacity($VARIABLE_COUNT);
            for i in 0..$VARIABLE_COUNT {
                used_variables.push($INTERVAL_INIT(COMP_DOUBLES[i]));
            }
            match std::panic::catch_unwind(|| $METHOD(&used_variables)) {
                Ok(result) => {
                    println!("{}: ", $TYPE_NAME);
                    print_query(lower(result), upper(result), $PRINT_METHOD(COMP_GMP_RATIONALS.as_slice()));
                }
                Err(_) => {
                    println!("{}: ", $TYPE_NAME);
                    print_query(1.0, -1.0, $PRINT_METHOD(COMP_GMP_RATIONALS.as_slice()));
                }
            }
        }
    }
}

The macro is used with the first function as the parameter $METHOD, e.g.

print_queries!(InariInterval, inari_interval, "INARI", square_root, print_square_root, 1);

As you can see, I'm using this method with the Inari crate, which is a crate for interval arithmetic : https://docs.rs/inari/latest/inari/index.html InariInterval is a copy of the type inari::Interval, and inari_interval is a method creating an Interval from one float.

However, this does not work, and here is the error I get :

error[E0277]: the trait bound `inari::Interval: num_traits::Float` is not satisfied
   --> src/query.rs:65:5
    |
65  |     run_query!(square_root, print_square_root, 1, square_root_range, check_input_square_root);
    |     ^^^^^^^^^^^-----------^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |     |          |
    |     |          required by a bound introduced by this call
    |     the trait `num_traits::Float` is not implemented for `inari::Interval`
    |
    = help: the following other types implement trait `num_traits::Float`:
              f32
              f64
note: required by a bound in `methods::square_root`
   --> src/methods.rs:803:30
    |
803 | pub(crate) fn square_root<T: Float>(value_array: &[T]) -> T {
    |                              ^^^^^ required by this bound in `square_root`

I'd like as much as possible to keep my functions generic. I don't think I can implement square_root without using Float, and that would only partly solve the problem since I have other functions with the same issue.

I tried to implement the Float trait for Inari but it doesn't seem to be possible. I got this error : Only traits defined in the current crate can be implemented for arbitrary types [E0117]

Thus, I'm a little bit stuck and I don't know if I can find a solution while keeping my functions generic.

Thanks for advance for your help and suggestions.


Solution

  • You have a couple options:

    • Ask the inari maintainers to add an impl, possibly behind a feature flag.
    • Make your own trait instead of using Float.
    • Make your own type instead of using Interval.

    The first would be ideal, but it may not be possible if Float is too specific for intervals. Otherwise, the second is probably the best, although you would then be responsible for implementing this trait for existing types, such as f32, f64, and other crates' floats. The third is similar to the second, but would make you responsible for implementing existing traits for your type.

    Also, don't use catch_unwind unless you absolutely need to. Panics are meant to be unrecoverable, so if you are expecting an error, use an Option or Result so that truly unrecoverable errors are not ignored.

    pub(crate) fn square_root<T: Float>(value_array: &[T]) -> Option<T> {
        // `first` is the same as `get(0)`
        value_array.first().map(|value| value.sqrt())
    }
    
    // inside the macro
    match $METHOD(&used_variables) {
        Some(result) => {
            println!("{}: ", $TYPE_NAME);
            print_query(lower(result), upper(result), $PRINT_METHOD(COMP_GMP_RATIONALS.as_slice()));
        }
        None => {
            println!("{}: ", $TYPE_NAME);
            print_query(1.0, -1.0, $PRINT_METHOD(COMP_GMP_RATIONALS.as_slice()));
        }
    }