Idiomatic way to express "multiple kinds, same data" case?

In my code, there is a couple of data structures containing the same data, but different "markers". I could express this in two general ways. First is to make a raw enum as a discriminant and then store it in the struct:

enum Kind {
    Kind1,
    Kind2,
    // maybe more
}

struct Thing {
    kind: Kind,
    // and some data fields
}

Second is to wrap the struct inside enum itself:

struct Data {
    // some data fields
}
enum Thing {
    Kind1(Data),
    Kind2(Data),
    // and possibly others
}

Which of these two is more idiomatic?

1 Like

The first pattern guarantees that the data is the same, so if you know it will always be the case, I'd go for that one. The second one, on the other hand, is more flexible to later on add some data to specific variants,

And obviously, you can write .view()-er methods to go from a borrow to one to the other:

enum Kind {
    _1,
    _2,
    // maybe more
}

struct Thing {
    kind: Kind,
    data: Data,
}

impl Thing {
    fn view (self: &'_ Thing) -> ThingView<'_>
    {
        match self.kind {
            | Kind::_1 => ThingView::Kind1(&self.data),
            | …
        }
    }
}

enum ThingView<'view> {
    Kind1(&'view Data),
    …
}

I think it really depends on whether your code more often deals with the data or the kind, although I'd probably prefer the former, especially if all the discriminants use the same data fields.

My feeling is that both are common and that one is not significantly better than the other in the general case. I tend to prefer 2 because it "forces" you to take the kind into account and is easier to change if you end up needing variants with different fields. That said there are situations where I would prefer 1 as well. If you access the data a lot and the kind isn't necessarily relevant when doing the accessing 2 can be a bit cumbersome.

1 Like

One way to deal with this is by implementing Borrow<Data> or Deref<Target=Data> for the enum (assuming all variants really do contain a Data instance).

Edit: Actually, AsRef is probably more appropriate than Borrow: Eq and Ord for the enum probably won’t agree with Data.

2 Likes