[rust-dev] Conflicting implementations of a trait

Sebastian Gesemann s.gesemann at gmail.com
Tue Jul 22 10:34:42 PDT 2014


Am 22.07.2014 18:50, schrieb Allen Welkie:
> Can there be two simultaneous implementations of a generic trait? I ask
> because I want to extend the Complex class to allow for multiplication by
> scalars, so that you can use "a * b" where "a" and "b" can be either
> scalars or Complex.

[snip]

Something like this was my first attempt in Rust. I was able to define
two own types (complex and imaginary) which I could mix with f64 for
multiplication, addition, etc.

But it required a kind of "double dispatch". Niko explained it here:
http://smallcultfollowing.com/babysteps/blog/2012/10/04/refining-traits-slash-impls/

Unfortunately, given how these traits are defined now, design requires a
bit of foresight. If you want to mix types like this for binary
operations eventually, you should probably start this kind of
dispatching early on. You can't do that with num's complex struct now.
Its Add/Mul/etc impls weren't designed with double-dispatch in mind. For
now, you would have to define your own types like I did.

But there is a chance that the binary operator traits change. For a
binary operator like + and * there is no clear "receiver" (an object you
call an add function on). IMHO the operands should be treated equally.
One approach that I saw mentioned by Niko (in another blog post I
believe) was to use tuples for that:

    trait Add<Out> {
        fn add(self) -> Out;
    }

    impl Add<Complex<f64>> for (f64,Compex<f64>) {
        fn add((lhs, rhs) : (f64, Complex<f64>)) -> Complex<f64> {
            ...
        }
    }

And this makes it much easier to extend the interface of certain types
together.

On the other hand, there still needs to go some thought into this with
respect to passing operands by value or reference. You don't want
unnecessary clones. And you probably don't want operands to be
moved-from in some cases. And the way these kinds of traits are refined
should work well together with generic code:

    fn foo<T,U,O>(x: T, y: U) -> O
        where ???: Mul<O>
    {
        x * y
    }

Ideally, this should work for every type T and U that can be multiplied
somehow. The question however is, how to write down the type bound?
Should we write

    (&T,&U): Add<O>

to avoid moving? Should we write

    (T,U): Add<O>

for a nicer, more intuitive syntax perhaps? I don't know. If you have a
good idea how to do that, I'm all ears. I'm very much interested in
getting easily overloadable operators without the pain of
double-dispatch and without the pain of clumsly type bounds for generic
functions that only work for half the cases due to references and such.

Cheers!
sg



More information about the Rust-dev mailing list