5
votes

I'm trying to write some generic math functions in Rust and I keep running into the following error message:

error: conflicting implementations for trait SoAndSo

Is it possible to solve the problem? If so, how?

For example, I'm trying to write a generic dot product that takes two iterators, zips them and iterates over the pairs to accumulate the products. I want this function also to be able to compute complex-valued dot products. The dot product over complex numbers involves conjugating one side. My first idea was to write a trait Dot1 for a binary function to replace Mul in that it also conjugates the left hand side argument. Here's the complete code:

extern crate num;

use num::complex::Complex;
use num::{Float, Num};

trait Dot1<Rhs, Result> {
    fn dot1(&self, rhs: Rhs) -> Result;
}

impl<T: Float> Dot1<T, T> for T {
    // conjugation for reals is a no-op
    fn dot1(&self, rhs: T) -> T {
        *self * rhs
    }
}

impl<T: Num + Clone> Dot1<Complex<T>, Complex<T>> for Complex<T> {
    fn dot1(&self, rhs: Complex<T>) -> Complex<T> {
        self.conj() * rhs
    }
}

fn main() {
    println!("Hello, world!")
}

Since a Complex<T> is not Float, there should be no overlap between the two "generic impls". I didn't expect any problems, but every time I try to provide more than one "generic impl" for a trait, the compiler doesn't like it:

error[E0119]: conflicting implementations of trait `Dot1<num::Complex<_>, num::Complex<_>>` for type `num::Complex<_>`:
  --> src/main.rs:17:1
   |
10 | impl<T: Float> Dot1<T, T> for T {
   | ------------------------------- first implementation here
...
17 | impl<T: Num + Clone> Dot1<Complex<T>, Complex<T>> for Complex<T> {
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `num::Complex<_>`
   |
   = note: upstream crates may add new impl of trait `num::Float` for type `num::Complex<_>` in future versions

How can I write a generic, iterator-based dot product that works for both real and complex numbers? Taking the iterators, zipping them, etc. is not a problem and I was even able to figure out what type parameters to use with which bounds. I don't seem able to "unify" certain data types using traits such as the one from above.

1
this will be possible when PR 48 is implementedArjan

1 Answers

2
votes

The problem here is the (current) compiler does not register that Complex<T> does not implement Float. Imagine if Complex did implement Float: either there would have to be some way to decide which impl to use, or the overlapping code would have to be outlawed... but what if someone only adds the impl<T> Float for Complex<T> in some other crate the compiler doesn't know about?

This last point is key: unlike Haskell, Rust's design allows this code to be perfectly OK without any risk, all because Rust differs in how it handles the last point. If you are in a crate that can see a type Type and a trait Trait, then you know for 100% sure if Type implements Trait: Rust doesn't have Haskell's open world assumption, as you can only write impl Trait for Type if either Trait or Type lie in the current crate (compilation unit), that is, you can't have orphan instances, where someone implements Float for Complex<T> in some distant crate.

RFC 24 (that Chris links to) recognises this, allowing these generic implementations to coexist with more specific ones, as long as the implementations are guaranteed to not overlap.