Rust
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Ownership and Borrowing
-
Section 4: Structuring Data
-
Section 5: Collections and Error Handling
-
Section 6: Traits and Generics
-
Section 7: Concurrency
-
Section 8: Building for the Web
-
Section 9: Memory and Performance
-
Section 10: More Standard Library and Ecosystem
-
Section 11: Advanced Rust
-
Section 12: Rust for Systems and WebAssembly
-
Section 13: Tooling and Best Practices
-
Section 14: Data Structures and Algorithms in Rust
-
Section 15: Practical Projects
-
Section 16: Interview Practice
-
Section 17: std::collections In Depth
-
Section 18: std::io and std::fs In Depth
-
Section 19: std::net
-
Section 20: std::option and std::result In Depth
-
Section 21: std::iter In Depth
-
Section 22: std::sync In Depth
-
Section 23: std::string and std::str
-
Section 24: Cargo and Crates.io Ecosystem
-
Section 25: Popular Crates Ecosystem
-
Section 26: Rust Memory Model Deep Dive
-
Section 27: More Practice Exercises
-
Section 28: More Interview Practice
-
Section 29: Async Rust Deep Dive
-
Section 30: Tokio Ecosystem In Depth
-
Section 31: Error Handling Ecosystem Deep Dive
-
Section 32: Serde In Depth
-
Section 33: Web Development with Rust Deep Dive
-
Section 34: Database Access Ecosystem
-
Section 35: Rust for Embedded Systems Deep Dive
-
Section 36: Rust Macros In Depth
-
Section 37: Advanced Trait System
-
Section 38: Unsafe Rust In Depth
-
Section 39: Rust CLI Development
-
Section 40: Testing Ecosystem Deep Dive
-
Section 41: WebAssembly Deep Dive
-
Section 42: Rust Design Patterns
-
Section 43: More Data Structures in Rust
-
Section 44: Final Practice Projects
-
Section 45: Rust Performance Optimization
-
Section 46: Rust Ecosystem Tooling
-
Section 47: More Interview and Review
37: Trait Objects and Dynamic Dispatch
You've probably reached a point in your project where you want a collection of different types that all share a common behavior. Maybe you're building a game and want a list of all "updatable" entities, or a UI framework where you have a list of different "widgets."
Here is a snippet of code that looks perfectly logical. I'm trying to create a simple drawing system where different shapes can be rendered to a screen.
trait Shape {
fn draw(&self);
}
struct Circle { radius: f64 }
struct Square { side: f64 }
impl Shape for Circle {
fn draw(&self) { println!("Drawing a circle!"); }
}
impl Shape for Square {
fn draw(&self) { println!("Drawing a square!"); }
}
fn main() {
// I want a list of shapes to draw every frame
let shapes: Vec = vec![
Circle { radius: 1.0 },
Square { side: 2.0 },
];
for shape in shapes {
shape.draw();
}
}
The "Sized" Constraint Wall
If you try to compile this, Rust will hit you with a wall of errors. The most important part is: the size for values of type `Shape` cannot be known at compilation time.
Here is what's happening: A Vec needs to know exactly how many bytes each element takes up so it can lay them out contiguously in memory. But Shape isn't a concrete type; it's a trait. A Circle might be 8 bytes, while a Square might be 8 bytes, but some other shape might be 100 bytes. Rust cannot put these directly into a Vec because it doesn't know how much space to allocate for "a Shape."
Switching to Dynamic Dispatch with Box<dyn Shape>
To fix this, we need to move the shapes from the stack to the heap. We do this using a Box, and we tell Rust we want a trait object using the dyn keyword.
A Box<dyn Shape> is a pointer. Regardless of whether the shape is a tiny Circle or a massive Polygon, the pointer itself is always the same size. This satisfies the Vec's need for a known size.
fn main() {
// We wrap each element in a Box and use 'dyn' to signal dynamic dispatch
let shapes: Vec> = vec![
Box::new(Circle { radius: 1.0 }),
Box::new(Square { side: 2.0 }),
];
for shape in shapes {
shape.draw();
}
}
How the Vtable Actually Works
When you use dyn Shape, you are opting into dynamic dispatch. In the previous example, if we had a Vec<Circle>, Rust would use static dispatchβit would know exactly which draw method to call at compile time and could even inline the code for speed.
With dyn, Rust can't do that. Instead, it creates a "vtable" (virtual method table). The Box<dyn Shape> actually becomes a fat pointer. It contains two things:
- A pointer to the data (the actual
CircleorSquareinstance). - A pointer to the vtable, which contains the addresses of the
Shapetrait methods for that specific type.
When you call shape.draw(), Rust follows the pointer to the vtable, finds the address for draw, and then jumps to that code. There is a tiny performance hit because of this extra indirection, but in 95% of application code, it's negligible compared to the flexibility it gives you.
When you can't use Trait Objects
One thing to watch out for: not every trait can be turned into a trait object. If your trait has a method that returns Self or uses a generic type, it is not object safe.
For example, if Shape had a method fn clone_me(&self) -> Self, you couldn't use dyn Shape. Why? Because the compiler wouldn't know how much space to allocate for the returned Selfβit's back to the same sizing problem we started with. If you see an error saying a trait "cannot be made into an object," check for Self or generics in your method signatures.
π Practical Task
Building a Multi-Channel Notification System
You are building a notification system that can send alerts via different channels (Email, SMS, and Slack). Each channel has a different way of "sending" the message, but the main application should be able to trigger all of them at once without knowing which is which.
Your Task:
- Define a trait called
NotificationChannelwith a methodsend(&self, message: &str). - Create three structs:
Email,Sms, andSlack. - Implement
NotificationChannelfor all three. Each should print a unique message (e.g.,"Sending Email: [message]"). - In your
mainfunction, create aVecthat holds a mix of these different channels using trait objects. - Iterate through the
Vecand callsendon each channel with the message"System Maintenance at Midnight!".
There are no comments for now.