Skip to content

Commit

Permalink
Re-work dynamic dispatch slides.
Browse files Browse the repository at this point in the history
Closes #185
  • Loading branch information
jonathanpallant committed Sep 24, 2024
1 parent ec52877 commit fe3ee57
Showing 1 changed file with 60 additions and 24 deletions.
84 changes: 60 additions & 24 deletions training-slides/src/dynamic-dispatch.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,25 +48,63 @@ impl Operation {

## Recommendation

Try to minimize repeated matches on the Enum, if not strictly necessary.
For best performance, try to minimize repeated matches on the `enum`.

See <https://godbolt.org/z/8Yf4751qh>

Note:

It takes multiple instructions to extract the tag from the enum and then jump to the appropriate block of code based on the value of that tag. If you use the Trait Objects we describe later, the *kind* of thing is encoded in the pointer to the dynamic dispatch table (or v-table) and so the CPU can just do two jumps instead of 'if this is 0, do X, else if this is a 1, do Y, else ...'.

## Trait Objects

References or raw pointers on traits, also boxes, describe so-called *trait objects*.
We can make references which do not know the type of the value but instead only know one particular trait that the value implements.

This is a *trait object*.

Internally, trait objects are a pair of pointers - one to a vtable and one the value itself.

Note:

Trait objects are a pair of pointers to a virtual function table and the data.
The term *vtable* is short for virtual dispatch table, and it's basically a struct full of function pointers that is auto-generated by the compiler.

## Usage

```rust
fn print(thing: &dyn std::fmt::Debug) {
// I can call `std::fmt::Debug` methods on `thing`
println!("{:?}", thing);
// But I don't know what the *actual* type is
}

fn main() {
print(&String::from("hello"));
print(&123);
}
```

## Limitations

- You can only use one trait per object
- Plus auto traits, like `Send` and `Sync`
- This trait must fulfill certain conditions

## Rules for object-safe traits (abbreviated)

- Object-safe traits are *not* allowed to require `Self: Sized`
- All methods are object-safe
* They have no type parameters
* They don't use `Self`
- Must not have `Self: Sized`
- No associated constants or GATs
- All methods must:
- Have no type parameters
- Not use `Self`, only `&self` etc
- Not return `impl Trait`

See [the docs](https://doc.rust-lang.org/reference/items/traits.html#object-safety) for details.

## Performance

There is a small cost for jumping via the vtable, but it's cheaper than an enum match.

See <https://godbolt.org/z/cheWrvM45>

## Trait Objects and Closures

Expand All @@ -80,29 +118,27 @@ fn factory() -> Box<dyn Fn(i32) -> i32> {
}
```

## Further properties
## Is this a reference to a String?

- As trait objects know about their exact type at runtime, they support downcasts through the `Any` trait.
Any type that is `'static + Sized` implements [`std::any::Any`](https://doc.rust-lang.org/stable/std/any/index.html).

We can use this to ask "is this reference actually a reference to *this specific type*?"

```rust []
use std::fmt::Debug;
use std::any::Any;

// Logger function for any type that implements Debug.
fn log<T: Any + Debug>(value: &T) {
let value_any = value as &dyn Any;
match value_any.downcast_ref::<String>() {
Some(string) => {
println!("String ({}): {}", string.len(), string);
}
None => {
println!("Not a String: {:?}", value);
}
fn print_if_string(value: &dyn std::any::Any) {
if let Some(s) = value.downcast_ref::<String>() {
println!("It's a string({}): '{}'", s.len(), s);
} else {
println!("Not a string...");
}
}

fn main() {
log(&"Some message".to_string());
log(&1)
print_if_string(&0);
print_if_string(&String::from("cookie monster"));
}
```

Note:

Be sure to check the documentation because `Any` has some important restrictions.

0 comments on commit fe3ee57

Please sign in to comment.