Ben Ruijl
Ben Ruijl

Reputation: 5143

Generic enum over groups of types

I want to abstract code acting on an enum of different professions ProfessionEnum, where each profession Student, Teacher, etc is a trait that has its own set of functions. I want the actual implementations of the traits to be grouped. For example:

I want TeacherA to be able to return StudentA and ProfessionEnums where all variants are in group A, but never of group B.

I also don't want to use any dyn. I tried implementing this using impls at first, but the language support for it is not ready yet. So I tried creating a trait Profession with associated types for each profession and then implement a ProfessionA where all associated types are of group A. Below is my attempt:

pub trait Profession {
    type T: Teacher;
    type S: Student;
}

pub trait Student {
    type P: Profession;
}

pub trait Teacher {
    type P: Profession;

    fn get_student(&self) -> <Self::P as Profession>::S;
}

pub enum ProfessionEnum<P>
where
    P: Profession,
{
    Student(P::S),
    Teacher(P::T),
}

// Concrete implementation of Profession, could be many
pub struct ProfessionA {}

impl Profession for ProfessionA {
    type T = TeacherA;
    type S = StudentA;
}

pub struct TeacherA {}

impl Teacher for TeacherA {
    type P = ProfessionA;

    fn get_student(&self) -> <Self::P as Profession>::S {
        StudentA {}
    }
}

pub struct StudentA {}

impl Student for StudentA {
    type P = ProfessionA;
}

// function generic over implementations of Profession
pub fn get_students<P: Profession>(p: ProfessionEnum<P>) {
    let mut students: Vec<P::S> = vec![];

    match p {
        ProfessionEnum::Student(s) => students.push(s),
        ProfessionEnum::Teacher(t) => students.push(t.get_student()), // ERROR
    }
}

fn main() {
    let p = ProfessionEnum::<ProfessionA>::Teacher(TeacherA {});
    get_students(p);
}

I get the following error:

error[E0308]: mismatched types
  --> src/main.rs:54:55
   |
49 | pub fn get_students<P: Profession>(p: ProfessionEnum<P>) {
   |                     - this type parameter
...
54 |         ProfessionEnum::Teacher(t) => symbol_buf.push(t.get_student()),
   |                                                  ---- ^^^^^^^^^^^^^^^ expected type parameter `P`, found associated type
   |                                                  |
   |                                                  arguments to this function are incorrect
   |
   = note: expected associated type `<P as Profession>::S`
              found associated type `<<<P as Profession>::T as Teacher>::P as Profession>::S`
   = note: you might be missing a type parameter or trait bound
note: associated function defined here

To me this was surprising at first, since I expected the type <<<P as Profession>::T as Teacher>::P as Profession>::S to be the same as <P as Profession>::S, but now I realise this is not guaranteed. I don't quite know how to restrict the type further.

Do you have any recommendations on how to restructure the code so that I can achieve the desired result?

Link to playground

Upvotes: 2

Views: 66

Answers (2)

Jmb
Jmb

Reputation: 23443

Does the teacher really need to be parametrized with the whole Profession? I would parametrize the teacher just with the student, then all you need is to add a where P::T: Teacher<S = P::S> clause to your get_students function:

pub trait Profession {
    type T: Teacher;
    type S: Student;
}

pub trait Student {
    type P: Profession;
}

pub trait Teacher {
    type S: Student;

    fn get_student(&self) -> Self::S;
}

pub enum ProfessionEnum<P>
where
    P: Profession,
{
    Student(P::S),
    Teacher(P::T),
}

// Concrete implementation of Profession, could be many
pub struct ProfessionA {}

impl Profession for ProfessionA {
    type T = TeacherA;
    type S = StudentA;
}

pub struct TeacherA {}

impl Teacher for TeacherA {
    type S = StudentA;

    fn get_student(&self) -> Self::S {
        StudentA {}
    }
}

pub struct StudentA {}

impl Student for StudentA {
    type P = ProfessionA;
}

// function generic over implementations of Profession
pub fn get_students<P: Profession>(p: ProfessionEnum<P>)
where P::T: Teacher<S = P::S>
{
    let mut students: Vec<P::S> = vec![];

    match p {
        ProfessionEnum::Student(s) => students.push(s),
        ProfessionEnum::Teacher(t) => students.push(t.get_student()), // ERROR
    }
}

fn main() {
    let p = ProfessionEnum::<ProfessionA>::Teacher(TeacherA {});
    get_students(p);
}

Playground

The same can be achieved if Teacher is associated to the profession by using where P::T: Teacher<P = P> (Playground)

Upvotes: 1

Chayim Friedman
Chayim Friedman

Reputation: 71440

This is pretty simple:

pub trait Profession {
    type T: Teacher<P = Self>;
    type S: Student<P = Self>;
}

Upvotes: 2

Related Questions