Help on enums and traits

Hi everyone,

I have taken a working example I found and extended it (I mean broke it) and now I can't figure it out.
I am still a beginner at Rust, but trying out new techniques...

My goal: I want to call exec() on OtherEnumA and OtherEnumB using dynamic dispatch

I have the below working, but there is an extra level of manual "match{}" code that just doesn't seem necessary and I want to find a way around it.

Thanks!!!

link to code: playground

Perhaps you are looking for something like the crate enum_dispatch.

use serde::{Deserialize};
use enum_dispatch::enum_dispatch;

#[enum_dispatch]
trait Executable {
    fn exec(&self) -> String;
}

#[derive(Debug, Deserialize)]
struct Example {
    field: i32,
    #[serde(flatten)]
    an_enum: AnEnum,
}

#[enum_dispatch(Executable)]
#[derive(Debug, Deserialize)]
enum AnEnum {
    A(OtherEnumA),
    B(OtherEnumB),
}

#[derive(Debug, Deserialize)]
enum OtherEnumA {
    AAA,
    BBB,
}
impl Executable for OtherEnumA {
    fn exec(&self) -> String {
        "Executing OtherEnumA...".to_string() 
    }
}

#[derive(Debug, Deserialize)]
enum OtherEnumB {
    XXX,
    YYY,
}
impl Executable for OtherEnumB {
    fn exec(&self) -> String {
        "Executing OtherEnumB...".to_string() 
    }
}

// I want this to work for types: OtherEnumA and OtherEnumB
fn generic_exec(cmd: &dyn Executable) {
    println!("\t{:?}", cmd.exec());
}

fn main() {
    let a = r#"{ "field": 42, "A": "AAA" }"#;  // Including "A" object
    let b = r#"{ "field": 42, "B": "YYY" }"#;  // Including "B" object

    let a: Example = serde_json::from_str(a).unwrap();
    let b: Example = serde_json::from_str(b).unwrap();

    println!("{:?}", a);
    println!("\t{:?}", a.an_enum); 
    generic_exec(&a.an_enum);

    println!("{:?}", b);
    println!("\t{:?}", b.an_enum);
    generic_exec(&b.an_enum);

}

But actually you still don’t need dynamic dispatch at all then. Change it to

fn generic_exec(cmd: &impl Executable) {
    println!("\t{:?}", cmd.exec());
}

and now generic_exec supports static dispatch (and you can by the way still pass a &dyn Executable to it if you want).


Edit: I should probably mention how it works without the macro. What enum_dispatch does in the code above is add an impl like this:

impl Executable for AnEnum {
    fn exec(&self) -> String {
        match self {
            AnEnum::A(x) => x.exec(),
            AnEnum::B(x) => x.exec(),
        }
    }
}

(playground)

Thank you for showing static dispatch w/o the macro!.

I really want to understand how to support both static and dynamic with the same code base.

In the example you gave on using dynamic dispatch, why do I have to still keep the

impl Executable for AnEnum {

line? If it is dynamic, why do we need the match{} ?

thx!

Okay, I’m having a bit of a hard time to fully understand what you’re after as I don’t see the need for dynamic dispatch in this example.

Maybe you want to not have AnEnum itself implement Executable, well, then you can’t call exec on AnEnum directly. To use dynamic dispatch with a &dyn Executable, you need to get the reference to the actual OtherEnum* inside AnEnum, which does require a match, too, but so what, here’s some code:

impl AnEnum {
    fn get_executable(&self) -> &dyn Executable {
        match self {
            AnEnum::A(x) => x,
            AnEnum::B(x) => x,
        }
    }
}

now we can do

generic_exec(a.an_enum.get_executable());

in main.

Oh, wait... I just noticed my &impl Executable version of generic_exec doesn’t like to take a dyn after all, I’ll have to change it to generic_exec(cmd: &(impl Executable + ?Sized)) for it to be as general as I promised it to be.


Maybe to get to something that dynamic dispatch is useful for: We can do stuff like fill an array with different kinds of executables (or in this case references to them) like for example this:

let executables = [
    a.an_enum.get_executable(),
    b.an_enum.get_executable(),
    &SomeEntirelyDifferentExecutable,
];

for &e in &executables {
    generic_exec(e)
}

(playground)

Thanks for the explanation, but a follow up. I have seen in some rust books/posts where the author will say you can use either static or dynamic dispatch, depending on your needs..In static, the compiler generates the multiple code paths, so it is (at least a little) faster, but has extra code size. And dynamic, is just the opposite. But I haven't found a simple case...There might not be a need for dynamic, but it would really help in understanding...

I wonder why the first example playground you gave me, why we can't easily convert to dynamic?

I updated the playground: playground

Thanks!

Yes, sure, I’ll elaborate a little bit. Take something like


#[derive(Debug)]
struct StructA {
    field: i32,
}
#[derive(Debug)]
struct StructB {
    foo: String,
    bar: f64,
}
#[derive(Debug)]
struct StructC;


trait SomeTrait {
    fn my_method(&self);
}
impl SomeTrait for StructA {
    fn my_method(&self) {
        println!("Called my_method on StructA: {:?}", self)
    }
}
impl SomeTrait for StructB {
    fn my_method(&self) {
        println!("Called my_method on StructB: {:?}", self)
    }
}
impl SomeTrait for StructC {
    fn my_method(&self) {
        println!("Called my_method on StructC: {:?}", self)
    }
}


fn my_complicated_function(x: &dyn SomeTrait) {
    x.my_method();
    println!("doing stuff that requires a lot of code");
    // a lot of code
    // a lot of code
    // a lot of code
    x.my_method(); // let’s call it a second time for good measure
}


fn main() {
    let a = StructA { field: 43 };
    let b = StructB { foo: "hello!".into(), bar: 3.14159265358979323 };
    let c = StructC;
    
    a.my_method();
    b.my_method();
    c.my_method();
}

This could be a reasonable way to prevent the compiler from creating multiple copies of my_complicated_function as it would be the case if its signature was generic like fn my_complicated_function<T: SomeTrait>(x: &T) (which can also be written fn my_complicated_function(x: &impl SomeTrait) equivalently).

Also please don’t confuse enums with inheritance. There is no OOP-style virtual methods in Rust; that’s not what is meant by dynamic dispatch. There’s none of this: if I statically dispatch I will reach this method but if I dynamically dispatch I will call that more specific method instead type of distinction. What even is dynamic dispatch? I suppose most often the term refers to calling function pointers instead of calling a function that’s hardcoded at compile-time. In this sense you can get dynamic dispatch in Rust from two features: function pointers and trait objects. That’s what such a dyn SomeTrait type is usually called.


I guess focusing less on the implementation details and more on the question _“what is a trait object”_ will help more in understanding why some Rust code works and what it does and what you can do in general, etc.

In Rust if you have a trait SomeTrait and that trait fulfills a few criteria called “object safety” then dyn SomeTrait is a type that also implements SomeTrait and any reference-type containing some type Foo with Foo: SomeTrait, like for example &Foo, Box<Foo>, Rc<Foo>, but also a bit more complex things like for example Rc<RefCell<Foo>> can be coerced to &dyn SomeTrait, Box<dyn SomeTrait>, Rc<dyn SomeTrait> or Rc<RefCell<dyn SomeTrait>> respectively.

The fact that dyn SomeTrait: SomeTrait then allows to call methods taking (&self) on a &dyn SomeTrait. Note that the type dyn SomeTrait is a bit weird in that it doesn’t really exist by itself but always sits behind a reference, similar to slices or str.

Now back to our dispatch topic: trait objects are implemented by making references like &dyn SomeTrait internally consist of two pointers: one to the object and one to a vtable for SomeTrait.

1 Like

Big thanks, appreciate the explanation!

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.