Question
Given this Rust trait:
pub trait Foo {
fn do_something(&self) -> f64;
}
how can a Bar struct store a value that implements Foo?
pub struct Bar {
foo: Foo,
}
This fails because a trait is used where Rust expects a concrete type. The compiler suggests using Box<Foo> or &Foo. Why does a struct field such as foo: &Foo require a lifetime specifier, while foo: Box<Foo> does not? Why can I not move a Box<Foo> out of &self in a method, and what does it mean when a getter takes self rather than &self?
Using current Rust syntax, the working version is:
trait Foo {
fn do_something(&self) -> f64;
}
struct Bar {
foo: Box<dyn Foo>,
}
impl Bar {
fn get_foo(self) -> Box<dyn Foo> {
self.foo
}
}
Short Answer
You will learn why traits need indirection when stored in structs, when to use Box<dyn Trait> versus &dyn Trait, how lifetimes describe borrowed fields, and why a method taking self can move a field while one taking &self cannot.
Concept
A trait such as Foo describes behavior, not one specific concrete memory layout. Many types can implement it, and those types may have different sizes:
struct Number;
struct Decimal(f64);
impl Foo for Number {
fn do_something(&self) -> f64 { 1.0 }
}
impl Foo for Decimal {
fn do_something(&self) -> f64 { self.0 }
}
Because Rust does not know the size of an arbitrary Foo implementation, dyn Foo is a dynamically sized type. A dynamically sized value cannot be stored directly as an ordinary struct field.
Instead, store a pointer-like value with a known size:
Box<dyn Foo>:Barowns the implementation, allocated on the heap.&'a dyn Foo:Barborrows an implementation owned elsewhere.Arc<dyn Foo>:Barshares ownership, often across threads when the trait object meets thread-safety bounds.
In modern Rust, write dyn Foo for a trait object. Older Rust code may show Box<Foo> or &Foo, but Box<dyn Foo> makes the dynamic dispatch explicit.
A trait object pointer contains enough information to call trait methods on an unknown concrete type. Conceptually, it carries a pointer to the value plus information about how to use it as Foo.
If you do not need runtime polymorphism, a generic type parameter is often another option:
struct GenericBar<T: Foo> {
foo: T,
}
This stores T directly and is usually compiled using static dispatch, but each T creates a distinct GenericBar<T> type.
Mental Model
Think of Foo as an interface for appliances, not an appliance itself.
- A struct field needs to know how much shelf space it occupies.
dyn Foocould be a toaster, a refrigerator, or another appliance with a different size, so it cannot occupy a fixed-size shelf slot directly.Box<dyn Foo>is a fixed-size label containing the location of an appliance thatBarowns.&'a dyn Foois a fixed-size borrowed-address card: it points to an appliance owned by someone else. The lifetime'ais the return deadline on that card:Barmust not keep it after the owner takes the appliance away.
For method receivers:
&selfmeans “borrow the box; do not take anything from it.”selfmeans “hand over the entireBar; its fields may now be moved out.”
Syntax and Examples
Use Box<dyn Foo> when the struct should own any value implementing the trait.
trait Foo {
fn do_something(&self) -> f64;
}
struct Temperature(f64);
impl Foo for Temperature {
fn do_something(&self) -> f64 {
self.0
}
}
struct Bar {
foo: Box<dyn Foo>,
}
impl Bar {
fn value(&self) -> f64 {
self.foo.do_something()
}
fn into_foo(self) -> Box<dyn Foo> {
self.foo
}
}
fn main() {
let bar = Bar {
foo: Box::new(Temperature(21.5)),
};
println!("{}", bar.value());
let foo = bar.into_foo();
println!("{}", foo.do_something());
}
value(&self) only reads through the box, so it borrows Bar.
into_foo(self) consumes Bar, allowing self.foo to move out. The name into_foo follows a common Rust convention: methods starting with into_ consume their receiver and convert it into another value.
Use a reference when another value owns the implementation:
trait Foo {
fn do_something(&self) -> f64;
}
struct Bar<'a> {
foo: &'a dyn Foo,
}
impl<'a> Bar<'a> {
fn value(&self) -> f64 {
self.foo.do_something()
}
}
The lifetime 'a says that the borrowed Foo must remain valid for at least as long as the Bar<'a> that points to it.
Step by Step Execution
Consider this owned trait-object example:
trait Foo {
fn do_something(&self) -> f64;
}
struct Fixed;
impl Foo for Fixed {
fn do_something(&self) -> f64 {
42.0
}
}
struct Bar {
foo: Box<dyn Foo>,
}
impl Bar {
fn read(&self) -> f64 {
self.foo.do_something()
}
fn into_foo(self) -> Box<dyn Foo> {
self.foo
}
}
fn main() {
let bar = Bar { foo: Box::new(Fixed) };
let answer = bar.read();
let foo = bar.into_foo();
println!("{answer}, {}", foo.do_something());
}
Execution and ownership flow:
Box::new(Fixed)allocates aFixedvalue and produces aBox<Fixed>.- Rust coerces
Box<Fixed>toBox<dyn Foo>becauseFixedimplementsFoo. bar.read()automatically borrowsbaras&Bar. The method readsfoothrough a shared borrow and returns42.0.baris still usable afterread, because nothing was moved out.bar.into_foo()passes ownership ofbarinto the method.- The method moves the
foofield out and returns it.barno longer exists afterward. - The returned box still owns
Fixed, so callingdo_something()remains valid.
This would be rejected:
impl Bar {
fn get_foo(&self) -> Box<dyn Foo> {
self.foo
}
}
self is only a shared borrow (&Bar) here. Moving the Box would leave the original Bar with a missing field, which is not allowed through a shared borrow.
Real World Use Cases
Trait-object fields are useful when a component must work with several implementations chosen at runtime.
- Pluggable storage: Store
Box<dyn Storage>so an application can use an in-memory store locally and a database-backed store in production. - Payment providers: Store
Box<dyn PaymentProcessor>to select Stripe-like, test, or regional implementations from configuration. - UI components: Keep heterogeneous widgets in
Vec<Box<dyn Widget>>when each widget implements the same drawing and event-handling trait. - Command handlers: Store different commands behind
Box<dyn Command>and execute each through a common method. - Testing: Pass
&dyn ClockorBox<dyn Clock>so production code uses a system clock while tests use a deterministic fake clock.
Choose a borrowed trait object when the dependency is created and owned by a higher-level object. Choose a boxed trait object when the containing struct should own the dependency independently.
Real Codebase Usage
In real Rust projects, trait objects are usually hidden behind constructors and accessed through behavior-focused methods rather than returned directly.
Constructor injection
trait Logger {
fn log(&self, message: &str);
}
struct Service {
logger: Box<dyn Logger>,
}
impl Service {
fn new(logger: Box<dyn Logger>) -> Self {
Self { logger }
}
fn run(&self) {
self.logger.log("service started");
}
}
This makes the dependency explicit and makes testing straightforward.
Prefer operations over exposing fields
Instead of returning the whole trait object, provide the operation callers need:
impl Bar {
fn result(&self) -> f64 {
self.foo.do_something()
}
}
This keeps Bar in control of its internal representation.
Borrow rather than transfer ownership
If a caller only needs to call trait methods, return a borrowed trait object:
impl Bar {
fn foo(&self) -> &dyn Foo {
self.foo.as_ref()
}
}
This does not move the Box; it returns a reference tied to the borrow of Bar.
Explicit ownership transfer
When a caller must take ownership of the implementation, use a consuming method and name it accordingly:
impl Bar {
fn into_foo(self) -> Box<dyn Foo> {
self.foo
}
}
A struct with Box<dyn Trait> is not automatically Clone. If code needs to duplicate it, the trait and design must explicitly support cloning, or the project may use shared ownership such as Arc.
Common Mistakes
Omitting dyn in current Rust
Older examples may use Box<Foo>. In current Rust, write:
Box<dyn Foo>
The dyn keyword makes it clear that calls use dynamic dispatch.
Storing a trait directly
This does not work:
struct Bar {
foo: dyn Foo,
}
dyn Foo has no compile-time-known size. Use a generic parameter or an indirection such as Box<dyn Foo>.
Forgetting the lifetime on a reference field
This does not compile:
struct Bar {
foo: &dyn Foo,
}
A field reference needs a lifetime because the struct may live beyond the scope where it was created. Correct version:
struct Bar<'a> {
foo: &'a dyn Foo,
}
Returning an owned box from &self
This tries to move out of a borrowed value:
impl Bar {
fn get_foo(&self) -> Box<dyn Foo> {
self.foo
}
}
Use one of these based on intent:
fn foo(&self) -> &dyn Foo {
self.foo.as_ref()
}
fn into_foo(self) -> Box<dyn Foo> {
self.foo
}
Expecting a value to remain usable after a self method
let bar = Bar { /* ... */ };
let foo = bar.into_foo();
// bar.read(); // Error: `bar` was moved.
A method taking self consumes the value unless its type implements Copy, which a Bar containing Box<dyn Foo> cannot do.
Using a trait that is not object-safe
Not every trait can become dyn Trait. For example, a method returning Self prevents ordinary trait-object use:
trait BadTrait {
fn make_another(&self) -> Self;
}
A trait object needs methods whose calls can be described without knowing the concrete implementing type. Keep traits intended for dyn Trait focused on object-safe methods.
Comparisons
| Approach | Ownership | Lifetime needed on Bar | Best when | Trade-off |
|---|---|---|---|---|
Box<dyn Foo> | Bar owns the value | No | Implementation is selected at runtime and should belong to Bar | Heap allocation and dynamic dispatch |
&'a dyn Foo | Another value owns it | Yes | Bar only needs temporary access to an existing dependency | Owner must outlive Bar |
T: Foo / Bar<T> | Bar owns T directly | No | Concrete type is known at compile time | One Bar<T> type per implementation; less flexible runtime selection |
Arc<dyn Foo> | Shared ownership | No | Several owners need the same implementation | Reference-counting overhead; threads may require Send + Sync |
&self versus self
| Receiver | Meaning | Can move self.foo out? | Typical use |
|---|---|---|---|
&self | Shared borrow | No | Read data or call non-mutating methods |
&mut self | Exclusive mutable borrow | Usually no for ordinary fields | Update fields in place |
self | Takes ownership | Yes | Consume, transform, or dismantle the value |
A generic field and a trait-object field solve related but different problems:
struct StaticBar<T: Foo> {
foo: T,
}
struct DynamicBar {
foo: Box<dyn Foo>,
}
Use StaticBar<T> when the implementation can be chosen at compile time. Use DynamicBar when one variable may hold different implementations at runtime.
Cheat Sheet
trait Foo {
fn do_something(&self) -> f64;
}
// Own an unknown implementation.
struct OwnedBar {
foo: Box<dyn Foo>,
}
// Borrow an implementation owned elsewhere.
struct BorrowedBar<'a> {
foo: &'a dyn Foo,
}
// Store a known-at-compile-time implementation directly.
struct GenericBar<T: Foo> {
foo: T,
}
impl OwnedBar {
// Read without consuming `self`.
fn value(&self) -> f64 {
self.foo.do_something()
}
// Borrow the contained trait object.
fn foo(&self) -> &dyn Foo {
self.foo.as_ref()
}
// Transfer ownership of the box.
fn into_foo(self) -> Box<dyn Foo> {
self.foo
}
}
- Prefer
dyn Fooover legacy bare trait-object syntax. - A
dyn Traitvalue needs indirection because its size is not known at compile time. - Reference fields require explicit lifetimes, such as
Bar<'a>. &selfborrows; it cannot move a non-Copyfield out.selfconsumes the struct, so fields can be moved out.- Use an
into_...method name for ownership-consuming conversions. - Trait objects require object-safe traits.
FAQ
Why does &dyn Foo need a lifetime in a struct?
A reference is borrowed data. Rust needs to know how long that borrow remains valid so that Bar cannot outlive the value it references. Write struct Bar<'a> { foo: &'a dyn Foo }.
Why does Box<dyn Foo> not need a lifetime parameter?
Box owns its allocation. The boxed value is dropped with the box, so no external owner needs to outlive Bar.
Can I return &dyn Foo from a struct containing Box<dyn Foo>?
Yes. This borrows the value instead of moving it:
fn foo(&self) -> &dyn Foo {
self.foo.as_ref()
}
Why can a method taking self return self.foo?
The method owns the entire Bar. It is therefore allowed to move the foo field into the return value. The original Bar is consumed.
Should I always use Box<dyn Trait> for traits in structs?
No. Use a generic T: Trait when the concrete type is known at compile time. Use Box<dyn Trait> when you need runtime polymorphism or heterogeneous implementations behind one field type.
Does Box<dyn Foo> copy the value when returned?
No. Returning it moves the Box, which transfers ownership of the allocation. The underlying value is not duplicated.
What does dyn mean in dyn Foo?
It indicates dynamic dispatch: Rust selects the appropriate trait-method implementation at runtime based on the concrete value stored behind the trait object.
Can every trait be used as dyn Trait?
No. The trait must be object-safe. In particular, methods that require knowing the concrete Self type can make a trait unsuitable for trait objects.
Mini Project
Description
Build a small report service that can use different score sources through one common trait. It demonstrates how a struct owns a runtime-selected implementation with Box<dyn ScoreSource>, how to call it through &self, and how to transfer it out with a consuming method.
Goal
Create a Report that prints a score from any type implementing ScoreSource and can later return ownership of that source.
Requirements
Create a ScoreSource trait with a method returning an f64 score.|Implement the trait for at least two different source types.|Store a Box<dyn ScoreSource> inside a Report struct.|Add a borrowed method that creates a report message without consuming the Report.|Add an into_source method that transfers the boxed source to the caller.
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.