7

Please have a look at the code:

struct Human {
  name: String,
  profession: Profession,
}

enum Profession {
  Doctor,
  Teacher
}

struct Family {
  family_doctor: // How to express type that will mean Human (because name matters) of profession Doctor?
}

Is it possible to do that, by making Human generic somehow, and pass variant Doctor as a type parameter for profession field? If not, then what is the closest workaround for such relationships you would suggest?

Please note that this question may seem as a duplicate of an older one. But firstly, Rust keeps evolving, secondly, I am asking for a workaround.

trent
  • 25,033
  • 7
  • 51
  • 90
Nurbol Alpysbayev
  • 19,522
  • 3
  • 54
  • 89

1 Answers1

6

Probably the easiest thing to do is to make Profession a trait rather than an enum, and make each Profession a unit struct:

struct Human<P: ?Sized + Profession> {
  name: String,
  profession: P,
}

struct Doctor;
struct Teacher;

trait Profession {}

impl Profession for Doctor {}
impl Profession for Teacher {}

struct Family {
  family_doctor: Human<Doctor>,
}

Most functions that accept Humans can be expressed using generics or impl Profession:

fn into_name<P: Profession>(any_human: Human<P>) -> String {
    any_human.name
}

Another way of writing functions that accept different kinds of Human is to use dynamic dispatch. If you do it this way, any access to a Human has to be done through a pointer, like Box<Human<dyn Profession>> or &Human<dyn Profession> (note this works because of P: ?Sized in the definition of Human):

fn into_name_dyn(any_human: Box<Human<dyn Profession>>) -> String {
    any_human.name
}

Yet another possibility is to implement Profession for Box<dyn Profession> and use that as the type parameter. This puts the pointer inside Human so only the Profession is behind it:

impl<P: ?Sized + Profession> Profession for Box<P> {}

fn into_name_box(any_human: Human<Box<dyn Profession>>) -> String {
    any_human.name
}

If you still want the enum, here's one suggestion: put the structs inside same-named variants. This way when you add behavior to the Profession trait, you can implement Profession for SomeProfession by deferring to the inner type's implementation. The enum_derive crate can make this process easier.

enum SomeProfession {
    Doctor(Doctor),
    Teacher(Teacher),
}

impl Profession for SomeProfession {}

fn into_name_enum(any_human: Human<SomeProfession>) -> String {
    any_human.name
}

See also

trent
  • 25,033
  • 7
  • 51
  • 90
  • 1
    This solution imply that an human can't change of profession. – Stargateur Dec 20 '19 at 15:56
  • 1
    Wow, this is an amazing answer! The last approach totally made sense to me, also looked nice. That said, in order to solve my original problem which is really duplication of fields, I am more inclined to use the composition approach, which is putting the duplicated fields into a struct. Your answer showed to me what are the options, and let me choose. – Nurbol Alpysbayev Dec 20 '19 at 16:32
  • 1
    What does `Human` refer to in the final example? – Maximilian Feb 14 '22 at 22:53