3

In Java-lingo, I have an interface R, an interface RT extends R (where RT implements all of R) and a bunch of other classes that all implement RT.

Transitioning to Rust I ended up with two traits

trait R { ... }
trait RT { ... }

where RT is a "subtrait" of R:

impl R for X where X: RT { ... }

Following that I have a bunch of structs, all of which implement RT:

struct RV { ... }
impl RT for RV { ... }

struct I { ... }
impl RT for I { ... }

struct U { ... }
impl RT for U { ... }

// ...

So far so good.

Now I want all of these structs to be comparable to each other, on the basis that all of them implement RT.

In Java I would change RT to

interface RT extends R, Comparable<RT>

and add a default implementation for equals and compareTo.

In Rust I have no idea if or how this could be approached.

I could say trait RT: PartialEq, but that would only make one implementation comparable with itself (RV == RV, but not RV == U).

My next idea was to add blanket implementations for every struct:

impl PartialEq<RV> for X where X: RT
impl PartialEq<I> for X where X: RT
// ...

I understand why this isn't allowed, however I'm still stuck with my initial problem.

I can't cast the values for comparison (RV as RT == U as RT) because RT can't be made into an object.

I could manually implement PartialEq<T> for every combination of structs but that would be a lot of duplication.

I considered using a macro to generate all the different implementations, but that feels so much like brute-forcing, that I question the initial design of my program.

How do I make all the different structs comparable to each other?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
xvlc0
  • 53
  • 4
  • 1
    "subtrait" would mean `trait RT: R { ... }` (which might also be something you want). You just have a regular trait and a "blanket `impl`" – trent Aug 22 '18 at 12:51
  • You are correct, that's why I put "subtrait" in quotes. Would `trait RT: R` make a difference to the current state of things? – xvlc0 Aug 22 '18 at 13:14
  • Not in terms of your actual question, no. I think it can make a difference to what the compiler can deduce about types in certain situations, but I can't think of an example right now. – trent Aug 22 '18 at 14:19
  • In Java, this line of reasoning makes sense because concrete classes implementing your interface can override the default implementation you've provided as necessary. In (stable) Rust, you *cannot* do that. Given this constraint, it doesn't make sense that you could implement equality checks for between any two types simply because they implement your `RT` trait. Usually, the more ideomatic thing to do in Rust is add constraints to your methods something along the lines of `>`. The exact constraint would depend on your requirements. – Wesley Wiser Aug 22 '18 at 15:15
  • Well, that answers my question, I guess. What do you mean by "stable" exactly. Is there something on the horizon in nightly rust? – xvlc0 Aug 23 '18 at 12:28
  • 1
    I believe (and I may be wrong) that they're referring to the fact that you can't currently have multiple overlapping implementations of a trait. The [specialization](https://github.com/rust-lang/rust/issues/31844) feature, which is currently only on nightly, intends to make it so you can override one impl with a more specific one. – Joe Clay Aug 23 '18 at 14:05
  • I believe your question is answered by the answers of [How to test for equality between trait objects?](https://stackoverflow.com/q/25339603/155423). If you disagree, please [edit] your question to explain the differences. Otherwise, we can mark this question as already answered. – Shepmaster Aug 23 '18 at 14:21
  • If I understand the linked response correctly, it only works with `'static` references, which does not apply to my problem. – xvlc0 Aug 25 '18 at 09:36
  • Possible duplicate of [How to test for equality between trait objects?](https://stackoverflow.com/questions/25339603/how-to-test-for-equality-between-trait-objects) – trent Jan 01 '19 at 13:33
  • It is not true that the answers to the linked question require `'static` references, so I have voted to close this one. – trent Jan 01 '19 at 13:36

1 Answers1

2

That pattern often arises in Java to emulate tagged unions, which are missing from the Java language. In Rust, unless you are writing a library whose users may need to define new implementations of RT, I suspect you’ll be happier with an enum instead of a trait object:

#[derive(PartialEq, Eq)]
enum AnyRT {
    RV(RV),
    I(I),
    U(U),
}

impl RT for AnyRT {
    fn foo(&self, ...) {
        match self {
            AnyRT::RV(rv) => rv.foo(...),
            AnyRT::I(i) => i.foo(...),
            AnyRT::U(u) => u.foo(...),
        }
    }
}

Depending on your application, you may then find that you don’t need the RT trait and/or the separate RV, I, U structs at all.

Anders Kaseorg
  • 3,657
  • 22
  • 35