Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

improve trait object doc #1443

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
286 changes: 219 additions & 67 deletions src/types/trait-object.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,89 +7,241 @@
> _TraitObjectTypeOneBound_ :\
> &nbsp;&nbsp; `dyn`<sup>?</sup> [_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
<abbr title="dynamically sized types">DSTs</abbr>, trait objects are used
behind some type of pointer; for example `&dyn SomeTrait` or
`Box<dyn SomeTrait>`. 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<dyn SomeTrait>`. 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<dyn Printable>) {
println!("{}", a.stringify());
impl Drawable for Circle {
fn draw(&self) {
println!("Drawing a circle");
}
}

impl ShapeAndDrawable for Circle {}

fn process_shapes(shapes: Vec<Box<dyn ShapeAndDrawable>>) {
for shape in shapes {
println!("Area: {:.2}", shape.area());
shape.draw();
}
}

fn main() {
print(Box::new(10) as Box<dyn Printable>);
let circle = Circle { radius: 3.0 };

let circle_trait_object: Box<dyn ShapeAndDrawable> = 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<T> {
fn process(&self, data: T);
}

struct StringProcessor;

impl Processor<&str> for StringProcessor {
fn process(&self, data: &str) {
println!("Processing string: {}", data);
}
}

fn process_data<T>(processor: Box<dyn Processor<T>>, data: T) {
processor.process(data);
}

fn main() {
let string_processor = StringProcessor;

let dynamic_processor: Box<dyn Processor<&str>> = 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<dyn Logger> = 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<dyn Transformer<Output = String>> =
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

Expand All @@ -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
Loading