Fluent Rust - Dynamic Dispatch

Migo·2023년 8월 21일
0

Fluent Rust

목록 보기
4/23
post-thumbnail

Dispatches

Dispatch is the process of selecting which implementation of a polymorphic operation (method or function)

If that happens at runtime, we call it dynamic dispatch whereas static dispatch is fully resolved during compile time. In this article, we'll cover what's involved in dynamic dispatch.

Why dynamic dispatch?

why do we need dynamic dispatch?
It's because we want to have a variable that implements a certain trait(and its methods). As the trait is essentially unsized, if you want to dynamically dispatch over values, the Rust compiler must at least have something on a stack frame that points to the unsized values on the heap memory whereby the compiler can tell the size of the pointer. The easiest solution is putting &. Imagine you want to initialize var with anything that implements CustomTrait as follows:

struct CustomStruct1;
struct CustomStruct2;

trait CustomTrait{}

impl CustomTrait for CustomStruct1{}
impl CustomTrait for CustomStruct2{}

fn main() {
    // Here, we want to initialize var with anything that implements CustomTrait
    let var: &mut dyn CustomTrait = if 1 == 1{
        let mut custom_struct1 = CustomStruct1;
        &mut custom_struct1
    } else {
        let mut custom_struct2 = CustomStruct2;
        &mut custom_struct2
    };

Trait Object

In the code above, I used trait object that is essentially a reference to the trait with dyn mark so it can hold both two different pointers - vptr and data. data pointer addresses the data(of any type, say X)), and the vptr points to the vtable corresponding to the implementation of the trait for the type X.

When trying to run the code above, the compiler will complain saying “borrowed value does not live long enough.” It is because both CustomStruct1 and CustomStruct2 were initialized in the inner block and went out of scope right away. To work around this, we can use deferred conditional initialization:

fn main() {
    let (mut custom_struct1, mut custom_struct2); // This makes sure that two variable live longer than `var`. 

    let var: &mut dyn CustomTrait= if 1 == 1{
        custom_struct1 = CustomStruct1;
        &mut custom_struct1
    } else {
        custom_struct2 = CustomStruct2;
        &mut custom_struct2
    };

}

But still, you will surely find it a bit verbose. And it turns out, using Box smart pointer will give you a much more succinct solution:

fn main() {
    let var: Box<dyn CustomTrait>= if 1 == 1{
        Box::new(CustomStruct1)
    } else {
        Box::new(CustomStruct2)
    };
}

Wait, why is it possible? because &dyn Trait is not only a trait object - it is just one of them. Listing off all of them, there are Box<dyn trait> Rc<dyn trait>, Arc<dyn trait>, and more. So among them, using &dyn Trait means that you have compiler take care of the lifetime which in this context is not necessary.

profile
Dude with existential crisis

0개의 댓글