Is it possible to create an enum with Trait objects?


#1

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?


#2

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.


#3

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…


#4

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>),
}

#5

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.


#6

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


#7

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.


#8

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!