Change function implementation from trait

I have some traits that are exclusives.

trait FirstTrait {
    fn first_function(&self, other: &Self) -> Self;
}

trait SecondTrait {
    fn second_function(&self, other: &Self) -> Self;
}

That is, if a type implement FirstTrait it can't implement SecondTrait. I am not imposing that in anyway except by documentation.

I also have a struct where I would like to change the implementation of a given function if a type implement a different trait.

struct SomeStruct<T> {
    values: Vec<T>
}

impl<T: FirstTrait> SomeStruct<T> {
    fn do_something(&self) -> T {
        self.values[0].first_function(&self.values[1])
    }
}

impl<T: SecondTrait> SomeStruct<T> {
    fn do_something(&self) -> T {
        // The implementation could be totally different
        // from the one where T: FirstTrait.
        self.values[0]
            .second_function(&self.values[1])
            .second_function(&self.values[2])
    }
}

However, the above doesn't compile since I am using the same function name twice. Of course, I could rename the functions to do_something_first_trait and do_something_second_trait. But, I find this solution ugly. Do you know if there is a better solution? Also, for some functions, the arguments could change too. Basically, I would just like to re-use the same name in different impl blocks.

negative trait bound is not possible.

rust does not support specialization. not in stable anyway. see https://github.com/rust-lang/rust/issues/31844

Unfortunately, Rust doesn't really allow this kind of thing.

This seems like an anti-pattern. Concretely, what are these two traits for?

I happened to see an article about how to define non-overlapping implementations, but haven't thought about OP.

The real problem is that I want to build a matrix structure that works with different mathematical objects. Concretely, finite fields, continuous fields and rings.

For example, when working with the integers modulo 6 (so for example 2 + 5 = 1 (mod 6)), I don't have access to division since they form a ring, but not a field (there is no number x such that 2x = 1 (mod 6) so 2 is not invertible). This change the implementation of some algorithm of the matrix data structure such as the calculation of the inverse of the matrix.

Another example is when I work with real numbers (f64), I can't compare numbers directly. I need to compare them with a given tolerance to account for floating point approximation errors. But, for integers, I don't need to worry about that and I can use simple equality to compare two numbers.

I would just like to specialize my function to avoid using different names depending on the implementation since, in the end, they represent the same concept, just obtained differently.

This doesn't really suggest that you should try and declare or implement non-overlapping traits. You should probably implement the same trait (eg. one containing matrix inversion) using different concrete algorithms for types that form a ring vs. a field.

Sounds like yet-another-case-where-Rust's-traits-ideals-cause-intense-pain.

Practically speaking what you really want is post-monomorphisation decisions which are explicitly forbidden in Rust. But there are one exception: constant calculation.

The least bad solution in my opinion is to move error detection to runtime (by “implementing” all operations for all types but making them panicable) and then moving checks back to compile time by [ab]using constants.

So we would have something like this:

trait MyOperations {
    fn add(&self);
    fn div(&self);
}

struct Field;

struct Ring;

impl MyOperations for Field {
    fn add(&self) {
        println!("Field: add is supported.")
    }
    fn div(&self) {
        println!("Field: div is supported.")
    }
}

impl MyOperations for Ring {
    fn add(&self) {
        println!("Ring: add is supported.")
    }
    fn div(&self) {
        panic!("Ring: div is not supported.")
    }
}

fn calculate(v: impl MyOperations) {
    v.add();
    v.div();
}

pub fn main() {
    let field: Field = Field;
    let ring: Ring = Ring;
    calculate(field);
    calculate(ring); // Panics at runtime.
}

And then you add guards like this:

trait MyOperations {
    const DIV_IS_SUPPORTED: () = { panic!("Div is not supported") };
    fn add(&self);
    fn div(&self) {
        panic!("Ring: div is not supported.")
    }
}

struct Field;

struct Ring;

impl MyOperations for Field {
    const DIV_IS_SUPPORTED: () = ();
    fn add(&self) {
        println!("Field: add is supported.")
    }
    fn div(&self) {
        println!("Field: div is supported.")
    }
}

impl MyOperations for Ring {
    fn add(&self) {
        println!("Ring: add is supported.")
    }
}

fn calculate1<T: MyOperations>(v: &T) {
    v.add();
}

fn calculate2<T: MyOperations>(v: &T) {
    _ = T::DIV_IS_SUPPORTED;
    v.add();
    v.div();
}

pub fn main() {
    let field: Field = Field;
    let ring: Ring = Ring;
    calculate1(&field);
    calculate1(&ring);
    calculate2(&field);
    //calculate2(&ring); Now that's compile time arror.
}

I would say it's still less ergonomic than languages with templates (like C++, D or Zig) would give you, but at least it gives you a way to solve your problem without ad-hoc hacks.

If you only have a few concrete types and you aren't trying to expose the generic type from SomeStruct, then you can just manually implement on each concrete type separately:

struct Field;
struct Ring;

struct SomeStruct<T> {
    values: Vec<T>
}

impl SomeStruct<Field> {
    fn do_something(&self) -> Field {
        // Use FirstTrait implementation on Field type
        Field
    }
}

impl SomeStruct<Ring> {
    fn do_something(&self) -> Ring {
        // Use SecondTrait implementation on Ring type
        Ring
    }
}

You could also extend that using generic newtype wrappers for the different implementations, perhaps bringing back your ability to expose the inner generic type while still enforcing the implementations to be non-overlapping:

trait FirstTrait {}
trait SecondTrait {}

struct Field<T: FirstTrait>(pub T);
struct Ring<T: SecondTrait>(pub T);

struct SomeStruct<T> {
    values: Vec<T>
}

impl<T: FirstTrait> SomeStruct<Field<T>> {
    fn do_something(&self) -> T {
        // Use FirstTrait implementation on Field (and its inner) type
        unimplemented!()
    }
}

impl<T: SecondTrait> SomeStruct<Ring<T>> {
    fn do_something(&self) -> T {
        // Use SecondTrait implementation on Ring (and its inner) type
        unimplemented!()
    }
}

You could also implement a common trait. Though it becomes a bit unwieldy, requires differentiating wrapper and inner types, and requires PhantomData. But here's a playground showing it works.

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.