Reputation: 647
I am trying to implement the following trait and struct:
pub trait Funct {
fn arity(&self) -> u32;
}
#[derive(Debug, Hash, Eq, PartialEq, Clone)]
pub struct FunctionLiteral<T: Funct> {
pub function: T,
pub args: Vec< FunctionLiteral<T> >
}
pub enum Foo {
Foo
}
impl Funct for Foo {
fn arity(&self) -> u32 {0}
}
pub enum Bar {
Bar
}
impl Funct for Bar {
fn arity(&self) -> u32 {0}
}
fn main() {
let baz = FunctionLiteral{
function: Foo::Foo,
args: vec![FunctionLiteral{
function: Bar::Bar,
args: vec![]
}]
};
}
I can set it up the way I have for the generic type T to be of trait Funct
, but I don't necessarily want T
to be of the same type.
Here, compiling the code gives the following error:
error[E0308]: mismatched types
--> foo.rs:31:23
|
31 | function: Bar::Bar,
| ^^^^^^^^ expected enum `Foo`, found enum `Bar`
error: aborting due to previous error
Is it possible to set up FunctionLiteral
such that I can have different types for function
and the items of args
, while forcing both of them to be of type Funct
?
Upvotes: 0
Views: 113
Reputation: 15723
When you do:
Structure<T: Trait>{
inner: T,
many: Vec<T>
}
You are telling the compiler to create a specialized instance for each different T
. So if you have Foo
and Bar
both implementing Trait
, then the compiler will generate two different representations, with two different sizes:
struct Foo(u8);
impl Trait for Foo{
// impl goes here
}
struct Bar(u64);
impl Trait for Bar{
// impl goes here
}
Then the compiler will generate something like:
Structure<Foo>{
inner: Foo,
many: Vec<Foo>
}
// and
Structure<Bar>{
inner: Bar,
many: Vec<Bar>
}
Obviously you cannot put Foo
instances into Bar
as they are different types and have different sizes.
You need to Box<>
your Funct
types in order to make them the same size (i.e. pointer sized). By putting them behind a (smart) pointer you are essentially erasing their types:
let a: Box<dyn Trait> = Box::new(Foo(0));
let b: Box<dyn Trait> = Box::new(Bar(0));
Now both a
and b
have the same size (the size of a pointer) and have the same type - Box<dyn Trait>
. So now you can do:
struct Structure{ // no longer generic!
inner: Box<dyn Trait>, // can hold any `Box<dyn Trait>`
many: Vec<Box<dyn Trait>> // can hold any `Box<dyn Trait>`
}
The downside of this approach is that it requires heap allocation and that it looses the exact type of a
and b
. Youno longer know if a
is Foo
or Bar
or something else.
Instead of Box
you can use any other smart pointer, such as Rc
or Arc
if you need its functionality.
Another option is to make Foo
and Bar
the same size and type. This can be done by wrapping them in an enum
:
enum Holder{
Foo(Foo),
Bar(Bar), // any other types go here in their own variants
}
Then your structure will look like:
struct Structure{ // no longer generic!
inner: Holder, // can hold any Holder variant`
many: Vec<Holder> // can hold any Holder variant`
}
The downside is that you have to either implement a delegation like:
impl Trait for Holder{
fn some_method(&self){
match self{
Holder::Foo(foo) => foo.some_method(),
Holder::Bar(bar) => bar.some_method(),
}
}
}
Or match
everywhere you want to use the object. Also now your Holder
enum will be the size of max(sizeof(Foo), sizeof(Bar))
On the plus side:
Upvotes: 2