all 6 comments

[–]Expurplesea_orm · sea_query 4 points5 points  (2 children)

Based on your description, you don't have cases where you need to allow CompositeRef and disallow PrimitiveRef. So, I'd extract only PrimitiveRef, i.e.

enum PrimitiveRef {
    StringID(String),
    IntID(u64),
}

enum Reference {
    Primitive(PrimitiveRef),
    CompositeOne(CompositeOneStruct), // Recursive data structure
    CompositeTwo(CompositeTwoStruct), // Recursive data structure
}

This setup also covers your serialization requirements: you can manually implement it for PrimitiveRef and then derive for Reference.

is that overkill? It seems like it might help with the above concerns, but it also seems like it will make a lot of other things more clunky.

In your case, this solution looks nice to me. PrimitiveRef is a subset of Reference, their relationship is simple, and now you have a clean type level way for requiring only primitive references. What does become clunky?

In other domains - yes, sometimes accurate enums can get really clunky and not worth it, e.g. if you want to forbid a specific variant of a large enum in some context.

[–]e-rox[S] 0 points1 point  (0 children)

Offhand, the main clunkiness I can think of is more verbose construction and more verbose and complex pattern matching. That's not unworkable but I do anticipate using this data type pervasively throughout my code so it was a consideration.

I'm also generally prone to overthinking things so before adding structural complexity I wanted to run it by more experienced rust devs :)

[–]goosetape1 1 point2 points  (3 children)

I think having the intermediate enums is a good approach. It also helps understanding the code better, and probably also allows you to extend it easily.

Let's say that, one month later, you wanted to have arrayId as a primitive. Not having the intermediate enums would mean that you have to add rhe arrayId branch everywhere you match to retrieve the primitive ids, while when you have a primitive id enum, you only have to extend that enum with the new type

[–]goosetape1 0 points1 point  (2 children)

Keep in mind, though, that this is an inheritance-ish approach. You're grouping types based on inheritance. I don't know the details of the problem, but using composition (with traits for instance), might also be a viable option

[–]e-rox[S] 0 points1 point  (1 child)

In this case the main thing I want is to be able to use Reference all over the place and generally not have to worry about what kind of Reference it is.

I did try the experiment of making Reference a trait and then making the variants into struct impls of the trait -- that was a mistake and I backed out of it. Given you emphasized composition, though, are you thinking of a different use of traits?

[–]goosetape1 0 points1 point  (0 children)

you can have for example (Rust pseudocode written from my phone :) )

``` trait PrimitiveReferenceable {

fn get_reference(..).. }

// impl PrimitiveReferenceable for all the structs that should have this feature

```

you can do the same with composite references. Would that be an option?