Question
What does the dyn keyword mean in Rust type annotations such as these?
fn foo(arg: &dyn Display) {}
fn bar() -> Box<dyn Display> {
// ...
}
How do &dyn Display and Box<dyn Display> work, and why is dyn used?
Short Answer
By the end of this page, you will understand that dyn Trait creates a trait object: a value whose concrete type is not known at compile time, but which is known to implement a particular trait. You will learn the difference between borrowing a trait object with &dyn Trait and owning one with Box<dyn Trait>, as well as the trade-off between dynamic and static dispatch.
Concept
dyn means dynamic dispatch in Rust.
A type such as dyn Display is a trait object type. It represents “some value of an unknown concrete type that implements Display.” Rust uses the trait's methods to interact with that value.
For example, both String and i32 implement Display:
use std::fmt::Display;
let text = String::from("hello");
let number = 42;
println!("{}", text);
println!("{}", number);
They have different concrete types, sizes, and memory layouts. A function accepting &dyn Display can accept either one because it only needs the behavior promised by Display.
use std::fmt::Display;
fn show(value: &dyn Display) {
println!("{value}");
}
fn main() {
let text = String::from("hello");
let number = 42;
show(&text);
show(&number);
}
The dyn keyword makes it explicit that Rust will choose the correct method implementation at runtime based on the value's actual type.
dyn Display alone is not a complete, storable value type because the concrete value may have any size. It must usually be used behind a pointer-like type that knows how to refer to dynamically sized data, such as:
&dyn Display— a borrowed trait object&mut dyn Display— a mutable borrowed trait objectBox<dyn Display>— an owned, heap-allocated trait objectArc<dyn Display>— a shared, thread-safe owned trait object when the trait bounds allow it
Trait objects are useful when a program needs one interface to work with values of multiple concrete types, especially when those types are selected at runtime.
Mental Model
Think of a trait as a job description.
Display says: “This value knows how to format itself for people to read.” A String can do that job, and an i32 can do that job, even though they are very different kinds of values.
A generic function is like hiring a worker whose exact profession is written into the plan before work begins:
fn show<T: Display>(value: &T) {
println!("{value}");
}
A dyn Display trait object is like receiving a sealed package with a label that says “contains a worker who can display itself.” You do not know the worker's exact concrete type, but you can still ask it to perform the Display job.
The reference or Box is the package handle. It lets Rust locate a value whose size and exact type are not known from the trait object type alone.
Syntax and Examples
The basic trait-object syntax is:
&dyn TraitName
&mut dyn TraitName
Box<dyn TraitName>
Here is a borrowed trait object:
use std::fmt::Display;
fn print_value(value: &dyn Display) {
println!("Value: {value}");
}
fn main() {
let name = "Ada";
let score = 98;
print_value(&name);
print_value(&score);
}
print_value does not care whether value is a string, number, or another type. It only requires that the value implements Display.
Here is an owned trait object stored in a Box:
use std::fmt::Display;
fn make_label(is_error: bool) -> Box<dyn Display> {
if is_error {
Box::new(String::from("Something went wrong"))
} else {
Box::new(200)
}
}
fn main() {
let label = make_label(false);
println!("{label}");
}
The two branches return different concrete types (String and i32). They can share the return type Box<dyn Display> because both types implement Display and are placed in a box.
In modern Rust, write dyn Display, not the older Display trait-object form. Rust permits the older form in limited compatibility situations, but dyn makes the dynamic-dispatch choice visible.
Step by Step Execution
Consider this example:
use std::fmt::Display;
fn announce(item: &dyn Display) {
println!("Item: {item}");
}
fn main() {
let quantity = 3;
announce(&quantity);
}
Step by step:
quantityis created as ani32with the value3.&quantitycreates a reference to thati32.- Rust converts that reference into a reference to a trait object:
&dyn Display. - The trait object stores enough information to:
- find the original
i32value, and - find the
Displayimplementation fori32.
- find the original
announcerunsprintln!("Item: {item}").- At runtime, Rust uses the stored implementation information to call
i32'sDisplayformatting behavior. - The program prints:
Item: 3
The same announce function could receive &String, &f64, or another type that implements Display.
Real World Use Cases
Trait objects are most useful when a program must choose among different implementations at runtime.
Plugin-like behavior
An application can represent different extensions through one trait:
trait Plugin {
fn run(&self);
}
struct Logger;
impl Plugin for Logger {
fn run(&self) {
println!("Writing a log entry");
}
}
struct CacheCleaner;
impl Plugin for CacheCleaner {
fn run(&self) {
println!("Cleaning the cache");
}
}
fn main() {
let plugins: Vec<Box<dyn Plugin>> = vec![
Box::new(Logger),
Box::new(CacheCleaner),
];
for plugin in plugins {
plugin.run();
}
}
Different response handlers
A server may select a handler based on configuration, command-line input, or an incoming request.
User-interface components
A UI may store different widget types, such as buttons, labels, and text fields, in one collection if they all implement a shared Draw trait.
Returning one of several concrete types
A factory function may choose between implementations without exposing every implementation type in its public return type.
Real Codebase Usage
In real Rust projects, trait objects often appear at boundaries where implementations vary but the caller should depend only on an interface.
Borrow a trait object when ownership stays with the caller
trait Notifier {
fn send(&self, message: &str);
}
fn notify_user(notifier: &dyn Notifier, message: &str) {
if message.is_empty() {
return;
}
notifier.send(message);
}
This is useful for dependency injection: production code can pass an email notifier, while tests can pass a fake notifier.
Store different implementations in a collection
trait Job {
fn execute(&self);
}
fn run_all(jobs: &[Box<dyn Job>]) {
for job in jobs {
job.execute();
}
}
A Vec<T> normally requires every item to have the same concrete type. Vec<Box<dyn Job>> solves this by giving every vector element the same outer type: a Box containing a Job trait object.
Return a selected implementation
trait Storage {
fn save(&self, key: &str, value: &str);
}
fn create_storage(use_memory: bool) -> Box<dyn Storage> {
if use_memory {
Box::new(MemoryStorage)
} else {
Box::new(FileStorage)
}
}
Use this pattern when configuration determines which implementation the application uses.
For simple, performance-sensitive code where the concrete type is known to the caller, generics are often the more common choice. Trait objects are chosen deliberately when runtime flexibility is more important.
Common Mistakes
Trying to use dyn Trait without a pointer
This does not work:
use std::fmt::Display;
// Invalid: a trait object does not have a compile-time-known size.
// let value: dyn Display;
Use a reference, Box, Arc, or another pointer-like wrapper instead:
let value: Box<dyn Display> = Box::new(42);
Expecting a vector to hold unrelated concrete types
This does not work because Vec requires one element type:
// Invalid
// let values = vec![String::from("hello"), 42];
Use trait objects if a shared trait is sufficient:
use std::fmt::Display;
let values: Vec<Box<dyn Display>> = vec![
Box::new(String::from("hello")),
Box::new(42),
];
Using Box<dyn Trait> when a borrowed value is enough
Heap allocation is not required just to call a trait method. Prefer &dyn Trait when the function only needs temporary access:
fn print(value: &dyn std::fmt::Display) {
println!("{value}");
}
Use Box<dyn Trait> when the code must own and store a value with an unknown concrete type.
Assuming every trait can become a trait object
Only object-safe traits can be used as dyn Trait. For example, traits with methods that return Self are commonly not object-safe:
trait Cloneable {
fn duplicate(&self) -> Self;
}
// `dyn Cloneable` is not allowed because the return type depends on `Self`.
A practical beginner rule: trait-object methods should generally avoid returning Self and avoid generic type parameters.
Forgetting lifetime requirements
A borrowed trait object is tied to the lifetime of its borrowed value:
fn show(value: &dyn std::fmt::Display) {
println!("{value}");
}
The caller must keep the original value alive for the duration of the call. A Box<dyn Trait> owns its contained value instead.
Comparisons
| Approach | Example | Concrete type known when? | Dispatch | Best for |
|---|---|---|---|---|
| Generic parameter | fn f<T: Display>(x: &T) | Compile time | Static | Reusable, performance-sensitive code |
| Borrowed trait object | fn f(x: &dyn Display) | Runtime | Dynamic | Accepting different implementations without taking ownership |
| Owned trait object | Box<dyn Display> | Runtime | Dynamic | Storing or returning one of several implementations |
| Enum | enum Value { Text(String), Number(i32) } | Known set at compile time | Match-based | A small, fixed set of variants you control |
Generics versus dyn
Generics create a specialized version of a function for each concrete type used. This is called static dispatch.
use std::fmt::Display;
fn generic_print<T: Display>(value: T) {
println!("{value}");
}
Trait objects call behavior through a runtime lookup mechanism, called dynamic dispatch.
use std::fmt::Display;
fn dynamic_print(value: &dyn Display) {
println!("{value}");
}
Generics are often faster to call and preserve more type information. Trait objects allow a single variable, parameter, or collection to work with different concrete types selected at runtime.
&dyn Trait versus Box<dyn Trait>
| Type | Owns the value? | Heap allocation by this type? | Typical use |
|---|---|---|---|
&dyn Trait | No | No | Temporarily use a caller-owned value |
&mut dyn Trait | No | No | Temporarily modify a caller-owned value through a trait |
Box<dyn Trait> | Yes | Yes | Store or return an owned implementation |
Cheat Sheet
// Borrowed trait object
fn print(value: &dyn std::fmt::Display) {
println!("{value}");
}
// Mutable borrowed trait object
fn update(value: &mut dyn SomeTrait) {
value.update();
}
// Owned trait object
let item: Box<dyn std::fmt::Display> = Box::new(42);
// A heterogeneous collection with a shared interface
let items: Vec<Box<dyn std::fmt::Display>> = vec![
Box::new("hello"),
Box::new(42),
];
dyn Traitmeans a trait object using dynamic dispatch.- A trait object hides the concrete type but exposes the trait's object-safe methods.
dyn Traitis dynamically sized, so use it behind&,Box,Arc, or a similar wrapper.- Use
&dyn Traitwhen you only need to borrow a value. - Use
Box<dyn Trait>when you need ownership or need to store different implementations together. - Prefer generics (
T: Traitorimpl Trait) when the concrete type can remain known at compile time. - Modern Rust style writes
dyn Traitexplicitly.
FAQ
What does dyn stand for in Rust?
dyn refers to dynamic dispatch. It indicates that method calls on the trait object are resolved using the value's concrete type at runtime.
Is dyn Display the same as Display?
In modern Rust trait-object syntax, use dyn Display. Older Rust code may use Display in trait-object positions, but dyn makes the meaning explicit.
Why does dyn Display need & or Box?
Different types implementing Display can have different sizes. Rust cannot know the size of a plain dyn Display value, so it must be accessed through a pointer-like wrapper.
When should I use &dyn Trait instead of Box<dyn Trait>?
Use &dyn Trait when the caller keeps ownership and your function only needs temporary access. Use Box<dyn Trait> when your code must own, store, or return the implementation.
Is dyn slower than generics in Rust?
A trait-object method call has a small dynamic-dispatch cost and may prevent some compiler optimizations. Generics use static dispatch, but can generate more compiled code. Choose based on your design needs rather than assuming one is always better.
Can every Rust trait be used with dyn?
No. The trait must be object-safe. Traits whose methods require knowing the concrete Self type, such as methods returning Self, often cannot be used as trait objects.
Can Box<dyn Trait> store values of different types?
Yes, as long as every stored value implements that trait. For example, a Vec<Box<dyn Display>> can store both strings and numbers.
Mini Project
Description
Build a small notification system that can send messages through different delivery methods. The program stores multiple notification services in one collection using Box<dyn Notifier>, demonstrating why trait objects are useful for runtime-selected behavior.
Goal
Create and run a list of different notifier implementations through one shared Notifier interface.
Requirements
- Define a
Notifiertrait with asendmethod that accepts a message. - Create an
EmailNotifierimplementation. - Create a
ConsoleNotifierimplementation. - Store both implementations in a single vector.
- Send the same message through every notifier.
Keep learning
Related questions
Absolute Values in Rust: Using the abs() Method
Learn how to calculate absolute values in modern Rust with abs(), handle signed integer overflow, and choose safer alternatives.
Accepting Iterators in Rust Functions with impl Iterator
Learn how to write Rust functions that accept HashMap iterators using generic bounds or impl Iterator, avoiding trait object and lifetime errors.
Access Enum Variant Values in Rust with Pattern Matching
Learn how to extract and print values stored in Rust enum variants using match and if let pattern matching, with practical examples.