Is it possible to create an enum with Trait objects?

I am looking to create an enum similar to the following -

enum Col {
   Numeric(Box<dyn Ord>),
   Categorical(Box<dyn Eq>)
}

This complains at me with the error

  |
2 |    Numeric(Box<dyn Ord>),
  |                ^^^^^^^ the trait `std::cmp::Ord` cannot be made into an object
  |
  = note: the trait cannot use `Self` as a type parameter in the supertraits or where-clauses

I could of course do something like

enum Col<N,C>
where
   N: Ord,
   C: Eq {
   Numeric(N),
   Categorical(C)
}

But then every time I create Col I would need to specify a type for N and C and of course they would then all be different types so I couldn’t just hold an array of lots of different Col objects…

Is there any way around this problem?

There are (somewhat weird) conditions under which a trait can be made into a trait object. Given the error message you get, I am afraid Ord violates some of these. I encountered similar issues before, but I do not know how to solve them, especially if, as here, you cannot change the definition of the trait in question.

Ah interesting. Yes this does compile :slight_smile:

trait Onk { }
trait Ponk { }

impl Onk for i64 { }

enum Col {
   Numeric(Box<dyn Onk>),
   Categorical(Box<dyn Ponk>)
}

fn main() {
    let x = Col::Numeric(Box::new(3));
    let y = Col::Numeric(Box::new(2));
}

So I guess I could conceivably do something with my own traits. It would be interesting to find out what those conditions are…

You’ll have to create your own traits that allow those operations over trait objects, something like:

trait Comparable {
    fn compare(&self, other: &dyn Comparable) -> std::cmp::Ordering;
}

trait Equatable {
    fn eq(&self, other: &dyn Equatable) -> bool;
}

enum Col {
    Numeric(Box<dyn Comparable>),
    Categorical(Box<dyn Equatable>),
}

The rules are in RFC 255. In particular, Ord uses Self in ways that are incompatible with a type-erased object. For instance, Ord::cmp can only compare to a reference of the exact same type (&Self), but the compiler can’t enforce that at a call site where the type isn’t known.

1 Like

Aaaah, ok. Yeah that does make a lot of sense. In my code I have stuff like

match (a, b) {
    (Numeric(a), Numeric(b)) => a > b,
    (Categorical(a), Categorical(b)) => a == b,
    (_, _) => panic!
}

So I guess the compiler is not trusting me to do the right thing if I were to compare a Numeric with a Categorical and saying it wants no part in such shenanigans!

Thanks

The compiler can properly stop you from comparing a Numeric with a Categorical. But in this line:

(Numeric(a), Numeric(b)) => a > b,

If you had Numeric(dyn Ord), then all we would know is that a can be compared to something of the same type as a, and likewise for b, but we don’t know that a and b can be compared to each other. They could be a String and a Vec<i32>, or who knows what else.

2 Likes

Of course… I hadn’t thought of that.

At the minute everything is just an integer or a string and I am having a go at making things more generic. That is a stumbling block that I hadn’t considered. Doh!

1 Like