How to make a trait group without Super Traits!

Hi! I has been writing projects and I noticed something, some times we have some... elements, lets say a number will be anything where we can +, -, *, /, anything that supports that operations, the only way I know to group them and request them as a condition for an algorithms is with something like:

trait: T: std::ops::Sub + std::ops::Div + std::ops::Add + std::ops::Mul {}

But this have an issue, for each type we want to use we need to implement the trait, not only that, we can have for example f64, but is a foreign type, so we can't implement it and we are forced to use wrappers....

In some project, specify with super traits to declare the functionality of elements is nice, but in others we just want to consider something as a "type" something that has some properties, is like think them implicitly.

So, going back, how can we make "something" that changes this:

fn foo<T: std::ops::Sub + std::ops::Div + std::ops::Add + std::ops::Mul>{}

to

Number: std::ops::Sub + std::ops::Div + std::ops::Add + std::ops::Mul;
fn foo1<T: Number>{}
fn foo2<T: Number>{}

The only solution know is use macros.

Thx!

That is not the case when the implementation resides in the same crate as the trait you are implementing. Orphan rules prevent you from implementing foreign traits on foreign types, not your own traits on foreign types.

You can do this using a blanket implementation for every T that implements Sub, Div, Add and Mul. There actually already exist such a trait, num::traits::NumOps with such a blanket implementation.

2 Likes

thx correcting me about the Orphan rule and the comment :slight_smile:

Still is just an example, any idea how to define an element in the way I put above?
This can happens with a lot of elements, and would be nice have a way to just say "I'll cal this group of traits with this name"

It's being added to the language but still unstable: trait_alias - The Rust Unstable Book

1 Like

Maybe I misunderstood you, but isn't num::traits::NumOps exactly what you are looking for?

use std::ops::{Add, Div, Mul, Rem, Sub};

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>
{
}

impl<T, Rhs, Output> NumOps<Rhs, Output> for T where
    T: Add<Rhs, Output = Output>
        + Sub<Rhs, Output = Output>
        + Mul<Rhs, Output = Output>
        + Div<Rhs, Output = Output>
        + Rem<Rhs, Output = Output>
{
}

fn foo<T: NumOps>() {}

fn main() {
    foo::<i32>();
    foo::<u32>();
    foo::<f32>();
}

Playground.

2 Likes

mm, it could be, I'm looking at the code, I have never seen before an use the impl block like that, I do not understand it very well.

If I make a new struct and implement the NumOps required traits (+-/*), but not the NumOps its self, will still works request NumOps?

Yes.

seems there is something off, this fails:

fn foo<T: num::traits::real::Real>(x: noisy_float::prelude::R64, y: T) {
        let s = x + y;
}

R64 has the Add operation, but using that method Rust says it do not have it...

Please post the full error message you get from running cargo check in your console.

1 Like
error[E0277]: cannot add `T` to `NoisyFloat<f64, FiniteChecker>`
 --> src/main.rs:6:19
  |
6 |         let s = x + y;
  |                   ^ no implementation for `NoisyFloat<f64, FiniteChecker> + T`
  |
  = help: the trait `Add<T>` is not implemented for `NoisyFloat<f64, FiniteChecker>`
help: consider introducing a `where` clause, but there might be an alternative better way to express this requirement
  |
5 | fn foo<T: num::traits::real::Real>(x: noisy_float::prelude::R64, y: T) where NoisyFloat<f64, FiniteChecker>: Add<T> {
  |                                                                        ++++++++++++++++++++++++++++++++++++++++++++

For more information about this error, try `rustc --explain E0277`.
error: could not compile `a` (bin "a") due to 1 previous error

Use Float instead of Real

1 Like

same error.

That is strange because there definitely is an implementation for NoisyFloat + Float

R64 is a NoisyFloat<f64, FiniteChecker> and the Add implementation specifies that you can only add F to NoisyFloat<F, C>, so you can only add an f64 to a NoisyFloat<f64, FiniteChecker>. However, what you have in the function foo is a type T that implements Float or Real, which may be f64 but is not necessarily guaranteed to be f64. You need to either just use f64 or pass the parameter to the NoisyFloat.

Edit: To clarify, here is what just using f64 would look like:

fn foo(x: noisy_float::prelude::R64, y: f64) {
        let s = x + y;
}

And this is what passing T to the NoisyFloat would look like:

fn foo<T: num::traits::float::Float>(x: noisy_float::NoisyFloat<T, FiniteChecker>, y: T) {
        let s = x + y;
}
2 Likes