Introduction
Rust needs to know the size of every type at compile time. A dyn Trait is a dynamically-sized type (DST) because it can represent any concrete type that implements the trait. The compiler rejects attempts to use dyn Trait directly as a value, requiring you to use a reference (&dyn Trait) or a pointer (Box<dyn Trait>) instead.
Symptoms
the size for values of type 'dyn Trait' cannot be known at compilation timedoesn't have a size known at compile-timethe trait 'Sized' is not implemented for 'dyn Trait'- Error occurs when returning
dyn Traitfrom a function - Error occurs when storing
dyn Traitin a struct field
Example error: ```rust trait Processor { fn process(&self, input: &str) -> String; }
fn create_processor() -> dyn Processor {
// error[E0277]: the size for values of type dyn Processor
// cannot be known at compilation time
TextProcessor::new()
}
Common Causes
- Returning
dyn Traitinstead ofBox<dyn Trait>from a function - Storing
dyn Traitdirectly in a struct field - Using
dyn Traitas a function parameter without reference - Attempting to create an array of
dyn Traitvalues - Generic function accidentally constrained to
dyn Trait
Step-by-Step Fix
- 1.Box the trait object for owned return values:
- 2.```rust
- 3.// Before
- 4.fn create_processor() -> dyn Processor {
- 5.TextProcessor::new()
- 6.}
// After: Box<dyn Trait> has known size (pointer size) fn create_processor() -> Box<dyn Processor> { Box::new(TextProcessor::new()) } ```
- 1.Use references for borrowed trait objects:
- 2.```rust
- 3.// For parameters, use references
- 4.fn run_processing(processor: &dyn Processor, input: &str) -> String {
- 5.processor.process(input)
- 6.}
// For return, return a reference with explicit lifetime fn get_default_processor() -> &'static dyn Processor { &DefaultProcessor } ```
- 1.Store trait objects in struct fields with Box:
- 2.```rust
- 3.// Before
- 4.struct AppConfig {
- 5.processor: dyn Processor, // Error: size not known
- 6.}
// After struct AppConfig { processor: Box<dyn Processor>, }
impl AppConfig { fn new(processor: Box<dyn Processor>) -> Self { AppConfig { processor } } } ```
- 1.Use generics instead of trait objects when possible:
- 2.```rust
- 3.// Runtime dispatch (trait object):
- 4.fn process_all(processors: &[Box<dyn Processor>], input: &str) -> Vec<String> {
- 5.processors.iter().map(|p| p.process(input)).collect()
- 6.}
// Compile-time dispatch (generics): faster, no boxing needed fn process_with<P: Processor>(processor: &P, input: &str) -> String { processor.process(input) } ```
- 1.**Use
impl Traitfor return type when concrete type matters**: - 2.```rust
- 3.// Instead of boxing a single return type:
- 4.fn create_processor() -> Box<dyn Processor> {
- 5.Box::new(TextProcessor::new())
- 6.}
// Use impl Trait (caller gets the concrete type): fn create_processor() -> impl Processor { TextProcessor::new() } ```
Prevention
- Default to generics (
impl Trait) when a single concrete type is returned - Use
Box<dyn Trait>only when you need heterogeneous collections - Consider
enuminstead of trait objects for a known set of types (faster dispatch) - Use
dyn Trait + Sendordyn Trait + Send + Syncfor cross-thread usage - Understand that
Box<dyn Trait>has a vtable pointer overhead (2 pointers total) - Prefer
&dyn TraitoverBox<dyn Trait>when borrowing is sufficient