From ea53e5d61c8fed1163a311cd047381cf48fb1201 Mon Sep 17 00:00:00 2001 From: wiseaidev Date: Thu, 28 Dec 2023 19:27:08 +0200 Subject: [PATCH] improve trait object doc --- src/types/trait-object.md | 286 +++++++++++++++++++++++++++++--------- 1 file changed, 219 insertions(+), 67 deletions(-) diff --git a/src/types/trait-object.md b/src/types/trait-object.md index 3526b7add..c310adeb8 100644 --- a/src/types/trait-object.md +++ b/src/types/trait-object.md @@ -7,89 +7,241 @@ > _TraitObjectTypeOneBound_ :\ >    `dyn`? [_TraitBound_] -A *trait object* is an opaque value of another type that implements a set of -traits. The set of traits is made up of an [object safe] *base trait* plus any -number of [auto traits]. - -Trait objects implement the base trait, its auto traits, and any [supertraits] -of the base trait. - -Trait objects are written as the keyword `dyn` followed by a set of trait -bounds, but with the following restrictions on the trait bounds. All traits -except the first trait must be auto traits, there may not be more than one -lifetime, and opt-out bounds (e.g. `?Sized`) are not allowed. Furthermore, -paths to traits may be parenthesized. - -For example, given a trait `Trait`, the following are all trait objects: - -* `dyn Trait` -* `dyn Trait + Send` -* `dyn Trait + Send + Sync` -* `dyn Trait + 'static` -* `dyn Trait + Send + 'static` -* `dyn Trait +` -* `dyn 'static + Trait`. -* `dyn (Trait)` - -> **Edition Differences**: Before the 2021 edition, the `dyn` keyword may be -> omitted. -> -> Note: For clarity, it is recommended to always use the `dyn` keyword on your -> trait objects unless your codebase supports compiling with Rust 1.26 or lower. - -> **Edition Differences**: In the 2015 edition, if the first bound of the -> trait object is a path that starts with `::`, then the `dyn` will be treated -> as a part of the path. The first path can be put in parenthesis to get -> around this. As such, if you want a trait object with the trait -> `::your_module::Trait`, you should write it as `dyn (::your_module::Trait)`. -> -> Beginning in the 2018 edition, `dyn` is a true keyword and is not allowed in -> paths, so the parentheses are not necessary. +A *trait object* is a powerful concept allowing you to work with values of +different types as long as they implement a specified set of traits. This set +consists of a primary, [object safe] *base trait*, and it can be extended with +any number of [auto traits]. + +A trait object represents a value of one type that adheres to the traits +specified in its definition. It not only implements the base trait but also +encompasses any associated [auto traits] and [supertraits] of the base trait. + +## Syntax and Examples + +The syntax for trait objects is concise: + +```rust +dyn? TypeParamBounds +``` + +Here, `dyn` is the keyword, and `TypeParamBounds` represents the set of traits +the object implements. While expressing trait bounds, some rules apply: only the +first trait can be non-auto, there should be at most one lifetime, and opt-out +bounds like `?Sized` are not allowed. Parentheses can be used to specify paths +to traits. + +For example, given a trait `Trait`, here are some trait objects: -Two trait object types alias each other if the base traits alias each other and -if the sets of auto traits are the same and the lifetime bounds are the same. -For example, `dyn Trait + Send + UnwindSafe` is the same as -`dyn Trait + UnwindSafe + Send`. +- `dyn Trait` +- `dyn Trait + Send` +- `dyn Trait + Send + Sync` +- `dyn Trait + 'static` +- `dyn Trait + Send + 'static` +- `dyn Trait +` +- `dyn 'static + Trait` +- `dyn (Trait)` -Due to the opaqueness of which concrete type the value is of, trait objects are -[dynamically sized types]. Like all -DSTs, trait objects are used -behind some type of pointer; for example `&dyn SomeTrait` or -`Box`. Each instance of a pointer to a trait object includes: +> **Edition Differences**: Before the 2021 edition, `dyn` could be omitted. However, +> for clarity, it is recommended to always use `dyn` unless your codebase supports +> Rust 1.26 or lower. - - a pointer to an instance of a type `T` that implements `SomeTrait` - - a _virtual method table_, often just called a _vtable_, which contains, for - each method of `SomeTrait` and its [supertraits] that `T` implements, a - pointer to `T`'s implementation (i.e. a function pointer). +> In the 2015 edition, if the first bound of the trait object is a path starting +> with `::`, then the `dyn` is considered part of the path. In such cases, use +> parentheses to avoid issues. -The purpose of trait objects is to permit "late binding" of methods. Calling a -method on a trait object results in virtual dispatch at runtime: that is, a -function pointer is loaded from the trait object vtable and invoked indirectly. -The actual implementation for each vtable entry can vary on an object-by-object -basis. +> Starting from the 2018 edition, `dyn` is a true keyword, and parentheses are not +> necessary. -An example of a trait object: +Two trait object types are considered identical if their base traits match, auto +traits are the same, and the lifetime bounds are identical. For instance, +`dyn Trait + Send + UnwindSafe` is equivalent to `dyn Trait + UnwindSafe + Send`. + +## Trait Objects in Action + +Due to the opaque nature of trait objects, they are considered +[dynamically sized types (DSTs)]. They are typically used with pointers like +`&dyn SomeTrait` or `Box`. A pointer to a trait object includes a +pointer to an instance of the implementing type and a _virtual method table_ +(vtable)containing function pointers for each method the type implements. + +The primary purpose of trait objects is to enable "late binding" of methods. +Invoking a method on a trait object results in virtual dispatch at runtime. +The correct function implementation is determined by loading a function pointer +from the trait object's vtable. + +## Example Code + +Let's consider some examples about trait objects: + +### 1. **Trait Object with Multiple Traits** ```rust -trait Printable { - fn stringify(&self) -> String; +trait Shape { + fn area(&self) -> f64; +} + +trait Drawable { + fn draw(&self); +} + +trait ShapeAndDrawable: Shape + Drawable {} + +struct Circle { + radius: f64, } -impl Printable for i32 { - fn stringify(&self) -> String { self.to_string() } +impl Shape for Circle { + fn area(&self) -> f64 { + std::f64::consts::PI * self.radius * self.radius + } } -fn print(a: Box) { - println!("{}", a.stringify()); +impl Drawable for Circle { + fn draw(&self) { + println!("Drawing a circle"); + } +} + +impl ShapeAndDrawable for Circle {} + +fn process_shapes(shapes: Vec>) { + for shape in shapes { + println!("Area: {:.2}", shape.area()); + shape.draw(); + } } fn main() { - print(Box::new(10) as Box); + let circle = Circle { radius: 3.0 }; + + let circle_trait_object: Box = Box::new(circle); + + process_shapes(vec![circle_trait_object]); } + +// Output + +// Area: 28.27 +// Drawing a circle ``` -In this example, the trait `Printable` occurs as a trait object in both the -type signature of `print`, and the cast expression in `main`. +This example defines two traits, `Shape` and `Drawable`, representing geometric shapes +and drawable objects. The `Circle` struct implements both traits. The `process_shapes` +function accepts a vector of trait objects that combine `Shape` and `Drawable`. In `main`, +we create a `Circle` instance, convert it to a trait object, and process it using the +generic function. + +### 2. **Dynamic Dispatch with Generics** + +```rust +trait Processor { + fn process(&self, data: T); +} + +struct StringProcessor; + +impl Processor<&str> for StringProcessor { + fn process(&self, data: &str) { + println!("Processing string: {}", data); + } +} + +fn process_data(processor: Box>, data: T) { + processor.process(data); +} + +fn main() { + let string_processor = StringProcessor; + + let dynamic_processor: Box> = Box::new(string_processor); + + process_data(dynamic_processor, "Hello, Trait Objects!"); +} + +// Output + +// Processing string: Hello, Trait Objects! +``` + +This example demonstrates dynamic dispatch with generics. The `Processor` trait has a +generic method, and we implement it for a specific type (`&str`). In `main`, we create +a `StringProcessor` instance, convert it to a trait object with dynamic dispatch, and +use it to process a string. + +### 3. **Trait Objects with Default Implementations** + +```rust +trait Logger { + fn log(&self, message: &str) { + println!("Default Log: {}", message); + } +} + +struct AppLogger; + +impl Logger for AppLogger { + fn log(&self, message: &str) { + println!("App Log: {}", message); + } +} + +fn main() { + let app_logger = AppLogger; + + let logger_trait_object: Box = Box::new(app_logger); + + logger_trait_object.log("Custom Log Message"); +} + +// Output + +// App Log: Custom Log Message +``` + +In this example, the `Logger` trait has a default implementation for the `log` method. +The `AppLogger` struct implements this trait, and in `main`, we create an instance, +convert it to a trait object, and use the default implementation to log a custom message. + +### 4. **Trait Objects and Associated Types** + +```rust +trait Transformer { + type Output; + fn transform(&self) -> Self::Output; +} + +struct UppercaseString(String); + +impl Transformer for UppercaseString { + type Output = String; + fn transform(&self) -> Self::Output { + self.0.to_uppercase() + } +} + +fn main() { + let uppercase_string = UppercaseString("hello".to_string()); + + let transformer_trait_object: Box> = + Box::new(uppercase_string); + + let result = transformer_trait_object.transform(); + println!("Transformed: {}", result); +} + +// Output + +// Transformed: HELLO +``` + +This example introduces a trait with an associated type. The `Transformer` trait has a +method `transform` with an associated type `Output`. The `UppercaseString` struct +implements this trait, and in `main`, we create an instance, convert it to a trait +object, and use it to transform a string. + +These examples cover a range of scenarios, from combining multiple traits to dealing +with generics, default implementations, and associated types, showcasing the flexibility +and power of trait objects in Rust. ## Trait Object Lifetime Bounds @@ -102,6 +254,6 @@ inferred with a sensible choice. [_TypeParamBounds_]: ../trait-bounds.md [auto traits]: ../special-types-and-traits.md#auto-traits [defaults]: ../lifetime-elision.md#default-trait-object-lifetimes -[dynamically sized types]: ../dynamically-sized-types.md +[dynamically sized types (DSTs)]: ../dynamically-sized-types.md [object safe]: ../items/traits.md#object-safety [supertraits]: ../items/traits.md#supertraits