Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Idiomatic Rust code guidance based on Apollo GraphQL's best practices handbook for ownership, errors, and performance.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
references/chapter_06.md
1# Chapter 6 - Generics, Dynamic Dispatch and Static Dispatch23> Static where you can, dynamic where you must45Rust allows you to handle polymorphic code in two ways:6* **Generics / Static Dispatch**: compile-time, monomorphized per use.7* **Trait Objects / Dynamic Dispatch**: runtime vtable, single implementation.89Understanding the trade-offs lets you write faster, smaller and more flexible code.1011## 6.1 [Generics](https://doc.rust-lang.org/book/ch10-00-generics.html)1213Every programming language has tools for effectively handling the duplication of concepts. In Rust, one such tool is generics: abstract stand-ins for concrete types or other properties. We can express the behavior of generics or how they relate to other generics without knowing what will be in their place when compiling and running the code.1415We use generics to create definitions for items like function signatures or structs, which we can then use with many different concrete data types. Let's first look at how to define functions, structs, enums, and methods using generics. Generics can also be used to implement Type State Pattern and constrain a struct functionality to certain expected types, more on type state on [Chapter 7](./chapter_07.md).1617[Generics by Examples](https://doc.rust-lang.org/rust-by-example/generics.html).1819### Generics Performance2021You might be wondering whether there is a runtime cost when using generic type parameters. The good news is that using generic types won't make your program run any slower than it would with concrete types. Rust accomplishes this by performing monomorphization of the code using generics at compile time. Monomorphization is the process of turning generic code into specific code by filling in the concrete types that are used when compiled. The compiler checks for all occurrences of the generic parameter and generates code for the concrete types the generic code is called with.2223## 6.2 Static Dispatch: `impl Trait` or `<T: Trait>`2425A static dispatch is basically a constrained version of a generics, a trait bounded generic, at compile-time it is able to check if your generic satisfies the declared traits.2627### โ Best when:28* You want **zero runtime cost**, by paying the compile time cost.29* You need **tight loops or performance**.30* Your types are **known at compile time**.31* Your are working with **single-use implementations** (monomorphized).3233### ๐๏ธ Example: High-performance function with generic34```rust35fn specialized_sum<T: MyTrait, U: Iterator<Item = T>>(iter: U) -> T {36iter.map(|x| x.random_mapping()).sum()37}3839// or, equivalent, more modern40fn specialized_sum<T: MyTrait>(iter: impl Iterator<Item = T>) -> T {41iter.map(|x| x.random_mapping()).sum()42}43```4445This is compiled into **specialized machine code** for each usage, fast and inlined.4647## 6.3 Dynamic Dispatch: `dyn Trait`4849Usually dynamic dispatch is used with some kind of pointer or a reference, like `Box<dyn Trait>`, `Arc<dyn Trait>` or `&dyn trait`.5051### โ Best when:52* You absolutely need runtime polymorphism.53* You need to **store different implementations** in one collection.54* You want to **abstract internals behind a stable interface**.55* You are writing a **plugin-style architecture**.5657> โ Closer to what you would get in an object oriented language and can have some heavy costs associated to it. Can avoid generic entirely and let you mix types that implement the same traits.5859### ๐ Example: Heterogeneous collection6061```rust62trait Animal {63fn greet(&self) -> String;64}6566struct Dog;67impl Animal for Dog {68fn greet(&self) -> String {69"woof".to_string()70}71}7273struct Cat;74impl Animal for Cat {75fn greet(&self) -> String {76"meow".to_string()77}78}7980fn all_animals_greeting(animals: Vec<Box<dyn Animal>>) {81for animal in animals {82println!("{}", animal.greet())83}84}85```8687## 6.4 Trade-off summary8889| | Static Dispatch (impl Trait) | Dynamic Dispatch (dyn Trait) |90|------------------- |------------------------------ |---------------------------------- |91| Performance | โ Faster, inlined | โ Slower: vtable indirection |92| Compile time | โ Slower: monomorphization | โ Faster: shared code |93| Binary size | โ Larger: per-type codegen | โ Smaller |94| Flexibility | โ Rigid, one type at a time | โ Can mix types in collections |95| Use in trait fn() | โ Traits must be object-safe | โ Works with trait objects |96| Errors | โ Clearer | โ Erased types can confuse errors |9798* Prefer generics/static dispatch when you control the call site and want performance.99* Use dynamic dispatch when you need abstraction, plugins or mixed types. ๐จ Runtime cost.100* If you are not sure, start with generics, trait bound them - then use `Box<dyn Trait>` when flexibility outweighs speed.101102> Favor static dispatch until your trait needs to live behind a pointer.103104## 6.5 Best Practices for Dynamic Dispatch105106Dynamic dispatch `Ptr<dyn Trait>` is a powerful tool, but it also has significant performance trade-offs. You should only reach for it when **type erasure or runtime polymorphism** are essential. It is important to know when you need Trait Objects:107108### โ Use Dynamic Dispatch When:109110* You need heterogeneous types in a collection:111```rust112fn all_animals_greeting(animals: Vec<Box<dyn Animal>>) {113for animal in animals {114println!("{}", animal.greet())115}116}117```118119* You want runtime plugins or hot-swappable components.120* You want to abstract internals from the caller (library design).121122123### โ Avoid Dynamic Dispatch When:124125* You control the concrete types.126* You are writing code in performance critical paths.127* You can express the same logic in other ways while keeping simplicity, e.g. generics.128129## 6.6 ๐จ Trait Objects Ergonomics130131* Prefer `&dyn Trait` over `Box<dyn Trait>` when you don't need ownership.132* Use `Arc<dyn Trait>` for shared access across threads.133* Don't use `dyn Trait` if the trait has methods that return `Self`.134* **Avoid boxing too early**. Don't box inside structs unless you are sure it'll be beneficial or is required (recursive).135```rust136// โ Use generics when possible137struct Renderer<B: Backend> {138backend: B139}140141// โ Premature Boxing142struct Renderer {143backend: Box<dyn Backend> // Boxing too early144}145```146* If you must expose a `dyn trait` in a public API, `Box` at the boundary, not internally.147* **Object Safety**: You can only create `dyn Traits` from object-safe traits:148* It has **no generic methods**.149* It doesn't require `Self: Sized`.150* All method signatures use `&self`, `&mut self` or `self`.151```rust152// โ Object Safe153trait Runnable {154fn run(&self);155}156157// โ Not Object Safe158trait Factory {159fn create<T>() -> T; // generic methods are not allowed160}161```162