Generic enum over groups of types - rust

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:
Group A: StudentA and TeacherA implement the respective traits using database calls
Group B: StudentB and TeacherB implement the respective traits using API calls
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

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

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)

Related

Unwrapping inner type from enum Rust multiple return type [duplicate]

This question already has answers here:
Unwrap inner type when enum variant is known
(4 answers)
Closed 7 months ago.
I have a function that returns an enum (an enum is used due to multiple possible return types).
But a method on one of the enum's variant type.
myTypeObject.print_type() <- This function call doesn't work
The code is below:
fn main() {
let myTypeObject = get_a_type(1);
myTypeObject.print_type();
}
struct TypeOne {
typename: String,
typeid: u8,
}
struct TypeTwo {
typename: String,
typeid: u8,
}
trait TraitOneTwo {
fn new(name: String) -> Self;
fn print_type(self);
}
impl TraitOneTwo for TypeOne {
fn new(name: String) -> Self {
Self {
typename: name,
typeid: 1,
}
}
fn print_type(self) {
println!("My type is: {}", self.typename);
}
}
impl TraitOneTwo for TypeTwo {
fn new(name: String) -> Self {
Self {
typename: name,
typeid: 2,
}
}
fn print_type(self) {
println!("My type is: {}", self.typename);
}
}
enum Types {
T1(TypeOne),
T2(TypeTwo),
}
fn get_a_type(desired_type: u8) -> Types {
if let 1 = desired_type {
return Types::T1(TypeOne::new(String::from("Foo")));
} else {
return Types::T2(TypeTwo::new(String::from("Bar")));
}
}
error[E0599]: no method named `print_type` found for enum `Types` in the current scope
--> src/main.rs:3:18
|
3 | myTypeObject.print_type();
| ^^^^^^^^^^ method not found in `Types`
...
45 | enum Types {
| ---------- method `print_type` not found for this
|
= help: items from traits can only be used if the trait is implemented and in scope
note: `TraitOneTwo` defines an item `print_type`, perhaps you need to implement it
--> src/main.rs:16:1
|
16 | trait TraitOneTwo {
| ^^^^^^^^^^^^^^^^^
I have tried defining the type as below, which still throws an error:
fn main() {
let myTypeObject: TypeOne = get_a_type(1).try_into().unwrap();
myTypeObject.print_type();
}
Error:
error[E0277]: the trait bound `TypeOne: From<Types>` is not satisfied
--> src/main.rs:2:47
|
2 | let myTypeObject: TypeOne = get_a_type(1).try_into().unwrap();
| ^^^^^^^^ the trait `From<Types>` is not implemented for `TypeOne`
|
= note: required because of the requirements on the impl of `Into<TypeOne>` for `Types`
= note: required because of the requirements on the impl of `TryFrom<Types>` for `TypeOne`
= note: required because of the requirements on the impl of `TryInto<TypeOne>` for `Types`
How may I invoke the print_type() function?
You could match on the enum like so:
fn main() {
let myTypeObject = get_a_type(1);
match myTypeObject {
Types::T1(t1) => {
t1.print_type()
}
Types::T2(t2) => {
t2.print_type()
}
}
}
here is the full example in a playground.
But the function in that enum cannot be invoked.
The error seems entirely clear:
no method named print_type found for enum Types in the current scope
The enum Types literally has no methods, how could you call that one on it?
The compiler even suggests one way to fix the issue:
TraitOneTwo defines an item print_type, perhaps you need to implement it
Now that doesn't really work because your trait definition doesn't really make sense: why is new part of the trait? But if you move new to intrinsic impls and then implement your trait on the enum, it works fine, kind-of:
impl TraitOneTwo for Types {
fn print_type(self) {
match self {
Self::T1(one) => one.print_type(),
Self::T2(two) => two.print_type(),
}
}
}
It's not clear why you need both a trait and an enum though.
NB: you could also make print_type an intrinsic method of the enum, as well as do everything in it directly
impl Types {
fn print_type(&self) {
println!(
"My type is: {}",
match self {
Self::T1(one) => &one.typename,
Self::T2(two) => &two.typename,
}
);
}
}
https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=dd097063943573b6d3b9c89643ba5a33
I don't really see the point of all the complexity, is this a java thing where you're supposed to use interfaces for everything?

How to solve "expected struct, found type parameter" on assignment?

Just started working with Rust a couple of days ago. I'm porting some C++ code right now, and this question seems to be the reverse of the common "expected struct, got type" sort. This code involves two classes, a container class A and a client class B.
use std::vec::Vec;
struct A<T:FooTrait> {
children: Vec<*mut T>
}
impl <T:FooTrait> A<T> {
fn insert(&mut self, val: &mut T) -> Handle<T> {
self.children.push(val);
return Handle{owner: self};
}
}
struct B {
handle: Handle<B>
}
trait FooTrait {
fn set_handle<T:FooTrait>(&mut self, h: Handle<T>);
}
impl FooTrait for B {
fn set_handle<B:FooTrait>(&mut self, h: Handle<B>) {
self.handle = h; // <-- Here is the problem
}
}
struct Handle<T:FooTrait> {
owner: *mut A<T>
}
impl <T:FooTrait> Default for Handle<T> {
fn default()->Handle<T> {
Handle {
owner: std::ptr::null_mut()
}
}
}
fn main() {
let mut foo = A::<B> { children: Default::default() };
let mut b = B{handle: Default::default()};
b.handle = foo.insert(&mut b);
}
Getting the error:
error[E0308]: mismatched types
--> src/main.rs:23:23
|
22 | fn set_handle<B:FooTrait>(&mut self, h: Handle<B>) {
| - this type parameter
23 | self.handle = h;
| ^ expected struct `B`, found type parameter `B`
|
= note: expected struct `Handle<B>` (struct `B`)
found struct `Handle<B>` (type parameter `B`)
Simplified version (playground):
use std::marker::PhantomData;
struct B {
handle: PhantomData<B>,
}
trait FooTrait {
fn set_handle<T: FooTrait>(&mut self, h: PhantomData<T>);
}
impl FooTrait for B {
fn set_handle<BType: FooTrait>(&mut self, h: PhantomData<BType>) {
self.handle = h;
}
}
Note that I've changed the name of the type parameter in set_handle. Now the error is more clear:
error[E0308]: mismatched types
--> src/lib.rs:13:23
|
12 | fn set_handle<BType: FooTrait>(&mut self, h: PhantomData<BType>) {
| ----- this type parameter
13 | self.handle = h;
| ^ expected struct `B`, found type parameter `BType`
|
= note: expected struct `std::marker::PhantomData<B>`
found struct `std::marker::PhantomData<BType>`
In your case, the error is essentially the same, since the generic parameter is a new type, which shadowed the global struct B.
Now, what to do? It depends on what do you want to get.
If the struct B definition is correct and set_handle need to handle only Bs, just remove the generic parameter from set_handle (playground):
trait FooTrait {
fn set_handle(&mut self, h: PhantomData<B>);
}
impl FooTrait for B {
fn set_handle(&mut self, h: PhantomData<B>) {
self.handle = h;
}
}
If the struct B definition is correct, but set_handle must be able to use different handler types depending on Self, use an associated type (playground):
trait FooTrait {
type T: FooTrait;
fn set_handle(&mut self, h: PhantomData<Self::T>);
}
impl FooTrait for B {
type T = B;
fn set_handle(&mut self, h: PhantomData<B>) {
self.handle = h;
}
}
Now the implementation block will choose what kind of argument (handler, in your case) it will get.
If the set_handle definition is correct, i.e. the caller can choose the type of handler, then struct B must be generic, too. However, in this case you essentially can't use the trait-based approach, since the trait has to be generic too, and you will not be able to simply use it in any generic bound without providing parameters (which has to be bound too, ad infinitum).
There are two different B here. The original B you defined in struct B, and the second <B:FooTrait> type parameter. The second is shadowing the first. Here is how the compiler sees it:
impl FooTrait for B {
fn set_handle<AnyFoo:FooTrait>(&mut self, h: Handle<AnyFoo>) {
self.handle = h; // <-- Here is the problem
}
}
The way you've defined things, FooTrait::set_handle is supposed to work with any type that implements FooTrait, but the handle field of B can only store a Handle<B>. You need to rethink what you want FooTrait to do and how B satisfies it.

Implementing From on a polymorphic type

I am writing a struct that functions as a thin wrapper over an existing struct. The existing struct isn't fixed, so I've written it as a type parameter (see struct B below). I would like B to preserve convertibility (i.e. From/Into) based on the internal wrapped type, i.e. if A1 is convertible to A then B<A1> should be convertible to B<A> as well.
I tried the following code:
pub struct A {}
pub struct A1 {}
impl From<A1> for A {
fn from(a1: A1) -> Self {
Self {}
}
}
pub struct B<T> {
t: T,
other_stuff: String,
}
impl<T, U: Into<T>> From<B<U>> for B<T> {
fn from(b: B<U>) -> Self {
Self {
t: b.t.into(),
other_stuff: b.other_stuff,
}
}
}
pub fn main() {
// I would like this to work:
let ba1: B<A1> = B{ t: A1{}, other_stuff: "abc".to_owned() };
let ba: B<A> = ba1.into();
}
It produces a compilation error:
error[E0119]: conflicting implementations of trait `std::convert::From<B<_>>` for type `B<_>`:
--> src/main.rs:13:1
|
13 | impl<T, U: Into<T>> From<B<U>> for B<T> {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: conflicting implementation in crate `core`:
- impl<T> std::convert::From<T> for T;
I think I understand why the error arises (because converting B<A> to B<A> is already defined), but how may I rewrite my code to avoid the error?

Is there a way to get objects from a HashSet<Rc<T>> using a custom Borrow implementation other than creating a newtype?

I have a Student struct which I store in a HashSet inside a School struct wrapped by Rc references so that they can be referenced by other internal structures in School:
use std::collections::HashSet;
use std::hash::{Hash, Hasher};
use std::rc::Rc;
#[derive(Debug, Eq)]
struct Student {
id: usize,
// other stuff
}
impl Hash for Student {
fn hash<H: Hasher>(&self, state: &mut H) {
self.id.hash(state);
}
}
impl PartialEq for Student {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
struct School {
students: HashSet<Rc<Student>>,
// other stuff
}
impl School {
fn new() -> Self {
Self {
students: HashSet::new(),
}
}
fn add_student(&mut self) -> usize {
let id = self.students.len();
self.students.insert(Rc::new(Student { id }));
id
}
}
I wanted to implement Borrow<usize> for Rc<Student> so I could get references to students from the HashSet using their id:
use std::borrow::Borrow;
impl Borrow<usize> for Rc<Student> {
fn borrow(&self) -> &usize {
&self.id
}
}
impl School {
fn enrol(&mut self, student_id: usize) {
// Need trait Borrow<usize> implemented for Rc<Student> for this to work
if let Some(student) = self.students.get(&student_id) {
println!("Enrolling {:?}", student);
}
}
}
Unfortunately I can't do that as Borrow is defined elsewhere, and the compiler is telling me I need to create a new type.
error[E0117]: only traits defined in the current crate can be implemented for arbitrary types
--> src/main.rs:26:1
|
26 | impl Borrow<usize> for Rc<Student> {
| ^^^^^-------------^^^^^-----------
| | | |
| | | `std::rc::Rc` is not defined in the current crate
| | `usize` is not defined in the current crate
| impl doesn't use only types from inside the current crate
|
= note: define and implement a trait or new type instead
I understand why a direct implementation of Borrow for Rc<T> is not possible but I'm wondering if there is a better way other than defining a new type just so I can implement Borrow. The goal is to have a shared reference but still be able to get objects from the HashSet by their id? Perhaps Rc isn't the best option here?

Why must the associated type be specified in a collection of trait object references?

Here is an offending example (Playground):
// Some traits
trait Behaviour {
type Sub: SubBehaviour;
}
trait SubBehaviour {}
// Some implementations of these traits
struct A;
impl Behaviour for A {
type Sub = B;
}
struct B;
impl SubBehaviour for B {}
// Struct that holds a collection of these traits.
struct Example<'a> {
behaviours: Vec<&'a dyn Behaviour>,
}
impl<'a> Example<'a> {
fn add_behaviour<T: Behaviour>(&mut self, b: &'a T) {
self.behaviours.push(b);
}
}
fn main() {
let b = A;
let mut e = Example {
behaviours: Vec::new(),
};
e.add_behaviour(&b);
}
I get:
error[E0191]: the value of the associated type `Sub` (from trait `Behaviour`) must be specified
--> src/main.rs:17:29
|
3 | type Sub: SubBehaviour;
| ----------------------- `Sub` defined here
...
17 | behaviours: Vec<&'a dyn Behaviour>,
| ^^^^^^^^^ help: specify the associated type: `Behaviour<Sub = Type>`
Why must this type must be specified, particularly in this case where we are only storing a reference to the object? How can I get this code to work?
All types must be statically known at compile time. If Rust would allow different associated types for elements of a Vec, type information could depend on indices which are only known at runtime.
I find it helpful to consider a smaller example:
trait Behaviour {
type T;
fn make_t(&self) -> T;
}
fn foo(my_vec: Vec<&dyn Behaviour>, index: usize) {
let t = my_vec[index].make_t(); //Type of t depends on index
}
You were on the right track to fixing this though. I assume you introduced the SubBehaviour trait because you realized you need to put restrictions of what T can be. The thing is, in that case you don't need an associated type anymore.
trait SubBehaviour {}
trait Behaviour {
fn make_t(&self) -> Box<dyn SubBehaviour>;
fn ref_t(&self) -> &dyn SubBehaviour; // also fine
}
fn some_function(my_vec: Vec<&dyn Behaviour>, index: usize) {
let t1 = my_vec[index].make_t();
}
The only limitation is that in your definition of Behaviour you can not do anything which would depend on the size of T, (like allocating it on the stack or moving it) since the size of T can not be specified by the SubBehaviour trait.
You need to specify the associated type of the trait (i.e. Behavior<Sub = ???>).
When adding the associated type at all places, it compiles:
struct Example<'a, S: SubBehaviour + 'a> {
behaviours: Vec<&'a Behaviour<Sub = S>>,
}
impl<'a, S: SubBehaviour> Example<'a, S> {
fn add_behaviour<T: Behaviour<Sub = S>>(&mut self, b: &'a T) {
self.behaviours.push(b);
}
}
See this in action on the Playground
So the answer to your first question is covered by Tim's answer and is correct, you might not want your Example to be generic. In that case, you need to use some sort of type erasure:
// Some traits
trait Behaviour {
type Sub: SubBehaviour;
}
trait SubBehaviour {}
// Some implementations of these traits
struct A;
impl Behaviour for A {
type Sub = B;
}
struct B;
impl SubBehaviour for B {}
struct AnyBehaviour {
closure: Box<Fn()>,
}
impl AnyBehaviour {
fn new<U: SubBehaviour, T: Behaviour<Sub = U>>(b: &T) -> Self {
let closure = || {
//let sub = T::Sub::new();
println!("Can use T here");
};
AnyBehaviour {
closure: Box::new(closure),
}
}
}
// Struct that holds a collection of these traits.
struct Example {
behaviours: Vec<AnyBehaviour>,
}
impl Example {
fn add_behaviour<U: SubBehaviour, T: Behaviour<Sub = U>>(&mut self, b: &T) {
self.behaviours.push(AnyBehaviour::new(b));
}
}
fn main() {
let b = A;
let mut e = Example {
behaviours: Vec::new(),
};
e.add_behaviour(&b);
}
Within the closure, you have access to all the types needed call the traits functions with whatever subtype needed.
Why this happens, is mostly because you actually need a definition of the associated type in order for the trait to be "complete" so the compiler can work with it. Tim's answer answers that by the definition to be higher up in the chain (outside of Example) instead of inside.

Resources