Trait bounds not satisfied even if they are defined explicitly

In mathematics, a field is a set on which addition, subtraction, multiplication and division are defined. I want to create a trait to define this for certain data types:

use std::ops::{Add, Div, Mul, Sub};
pub trait FieldOps<T>:
    Add<Output = T> + Sub<Output = T> + Mul<Output = T> + Div<Output = T>
{}

Now I don't just want to allow these operations when they consume the variable but also by reference so I added the following to expand the trait:

impl<'a, T: 'a> FieldOps<T> for &'a T where
    &'a T: Add<Output = T> + Sub<Output = T> + Mul<Output = T> + Div<Output = T>
{}

Thus my understanding at this point is that if FieldOps can be implemented on a type then it is capable of doing things like T+T->T, &T+&T->T etc. Because these operations must have been defined in order for T to allow the implementation of FieldOps.

However if I now try to create a simple function such as the following:

fn add<T>(x: &T, y: &T) -> T
where T: FieldOps<T>
{ x + y }

Then I will receive the compiler error:

16 | { x + y }
   |   - ^ - &T
   |   |
   |   &T
   |
help: consider extending the `where` clause, but there might be an alternative better way to express this requirement
   |
15 | where T: FieldOps<T>, &T: Add<&T, Output = T>
   |                     +++++++++++++++++++++++++

What do I not understand here?


If I instead use:

fn add<T>(x: &T, y: &T) -> T
where for<'a> &'a T: FieldOps<T>, 
{ x + y }

then it will compile but I would have thought the implementation of the trait covers this directly?

This is definitely a point of frustration in implementing math over generic types. The Rust standard library uses a fwd_ref_binop macro to call Deref on &T to drop back to use the bare T implementation for Copy types. But it doesn't make naming the trait any easier, it just allows more flexibility with known types (f64, etc.).

And, more to that point, if T is Copy then the "consuming" operations won't actually consume the value because Copy semantics are used. You may be best served taking how things like nalgebra work and just care about "closed" ops (trait ClosedAdd<T>: Add<T, Output = Self>), and try not to worry about owned vs. referenced "element" values.

That should let you blanket implement over anything "with elements". The where signature for math on an nalgebra matrix is a lot, but it's all based on T: Scalar + ClosedAddAssign

Unfortunately my data types are not Copy but only Clone. But I will take a look at the nalgebra examples. Thx

Then you might be looking for a trait alias to name the various combinations of owned/ref:

trait AllSelfAddOps = Add<Self, Output = Self>
    + for<'o> Add<&'o Self, Output = Self>
where
    for<'s> &'s Self: Add<Self, Output = Self>
        + for<'o> Add<&'o Self, Output = Self>;
1 Like

Only supertrait bounds (bounds on Self) are implied.

1 Like

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.