OOP and weighted random selection with enums

I'm new to Rust and am using it for my final university project. Would appreciate any advice on anything!

I'm using enums to represent some form of polymorphism. Here's a minimal example which is equivalent to what I'm trying to do. I'm not using dynamic dispatch with traits since the subcategories (the enum tree) are fixed, and in my actual implementation of the behaviour of each enum is relatively different.

enum Worker {
    Doc(Doctor),
    Eng(Engineer),
    Law(Lawyer),
}

enum Doctor {
    Surgeon(Surgeon),
    Vet(Vet),
}

enum Engineer {
    Mechanical(MechanicalEngineer),
    Software(SoftwareEngineer),
}
struct Lawyer {}
struct Surgeon {}
struct Vet {}
struct MechanicalEngineer {}
struct SoftwareEngineer {}

I'd also like to implement a random distribution for each enum, but when I match them this would mean that I have to instantiate the struct/enum within the enum variant since I'm using a tuple enum.

i.e. a distribution for each enum, I've provided an example for what it would have to look like for the Engineer enum:

impl Distribution<Engineer> for Standard {
    fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Engineer {
        match rng.gen_range(0..=1) {
            0 => Engineer::Mechanical(MechanicalEngineer {}),
            1 => Engineer::Software(SoftwareEngineer {}),
        }
    }
}

Is there a way to get around this or make this work? Should I create a separate set of enums ONLY for the distribution? I feel like I may be too greedy with this approach of trying to do both with a single set of enums.

I don't think I understand it correctly. Why do you need to match it while constructing itself? Does the tuple enum make any difference?

I think I said that part wrong, I won't need to construct upon matching the randomly selected result. The random selection function itself must instantiate the struct/enum inside the variant though. In the second code listing I provide an example of how this happens.

I'd like to be able to randomly sample an enum's variants, and based on what I've sampled, perform some action. In action it would look like this:

// Here engineer will return an enum variant with a struct contained
// The struct instantiated inside the tuple enum doesn't mean anything
let engineer: Engineer = rand::random();  

match engineer {
    Engineer::Mechanical(_) => (), // do something (but not with _)
    Engineer::Software(_) => (), 
}

You simply can't instantiate an enum variant without instantiating the associated data fields, if any. If you don't need the associated data, don't use/instantiate it. If you merely want some sort of distribution, sampling random integers should suffice.

Ideally I'd like to tie my distribution to each enum itself, so a Worker may have a 25% chance to be a Doctor, etc. The nice thing about this is I could call rand::random() and have that propagate and select some random variant for me.

Sampling random integers is definitely a consideration I had, just thought that it wouldn't look as clean and would involve lots of consts (one for each variant). I'd also still end up returning an enum with a useless associated data field too.

I don't have a great feel for whether you should or not, but there are crates like strum which can help facilitate patterns like this in various ways (non-data-loaded sibling enum, const array of variant names, ...).

Another pattern which may or may not work for you is to separate out the "kind" of the Engineer (say) from the data. Especially if all the kinds have the same data fields. Something like:

// No associated data
enum EngineerKind {
    Mechanical,
    Software,
}

struct Engineer {
    kind: EngineerKind,
    data: SomeType,
    // etc
}

Then you only have one non-data-loaded enum per category.

1 Like

strum provides the exact solution for all my needs, thanks!

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.