Alex Coleman
Alex Coleman

Reputation: 647

Struct fields should be all of same trait, but not neceesarily same type

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

Answers (1)

Svetlin Zarev
Svetlin Zarev

Reputation: 15723

The problem

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.

The solution

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

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:

  • you still know the actual type - it's not erased
  • no heap allocation

Upvotes: 2

Related Questions