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.
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:
Oh, wait... I just noticed my&impl Executableversion ofgeneric_execdoesn’t like to take adynafter all, I’ll have to change it togeneric_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)
}
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?
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.