Reputation: 5143
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:
StudentA
and TeacherA
implement the respective traits using database callsStudentB
and TeacherB
implement the respective traits using API callsI want TeacherA
to be able to return StudentA
and ProfessionEnum
s 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 impl
s 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?
Upvotes: 2
Views: 66
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);
}
The same can be achieved if Teacher
is associated to the profession by using where P::T: Teacher<P = P>
(Playground)
Upvotes: 1
Reputation: 71440
This is pretty simple:
pub trait Profession {
type T: Teacher<P = Self>;
type S: Student<P = Self>;
}
Upvotes: 2