Returning Trait Objects
When a function must return different concrete types across conditional branches that implement a common trait, returning a boxed trait object (-> Box<dyn Trait>) erases the concrete return type behind a uniform 16-byte pointer.
Why Conditional Branches Require Box<dyn Trait> (Not impl Trait)
An opaque return type (-> impl Trait) is resolved at compile time to a single concrete type, so returning two different structs from if / else or match arms fails with E0308. Returning Box<dyn Trait> unifies all branches into the same return type:
trait Exporter {
fn export(&self, rows: &[&str]) -> String;
}
struct CsvExporter;
impl Exporter for CsvExporter {
fn export(&self, rows: &[&str]) -> String {
rows.join(",")
}
}
struct JsonArrayExporter;
impl Exporter for JsonArrayExporter {
fn export(&self, rows: &[&str]) -> String {
let quoted: Vec<String> = rows.iter().map(|r| format!("\"{r}\"")).collect();
format!("[{}]", quoted.join(", "))
}
}
// Factory function selecting a concrete implementation at runtime:
fn create_exporter(format: &str) -> Result<Box<dyn Exporter>, String> {
match format {
"csv" => Ok(Box::new(CsvExporter)),
"json" => Ok(Box::new(JsonArrayExporter)),
other => Err(format!("unsupported format: {other}")),
}
}
fn main() {
let data = ["rust", "systems", "2024"];
for fmt in ["csv", "json"] {
let exporter = create_exporter(fmt).unwrap();
println!("{fmt:<4} -> {}", exporter.export(&data));
}
}
csv -> rust,systems,2024
json -> ["rust", "systems", "2024"]
Returning Trait Objects That Borrow Input Data (Box<dyn Trait + 'a>)
By default, -> Box<dyn Trait> is shorthand for -> Box<dyn Trait + 'static>. If the returned trait object wraps a struct or iterator that borrows from an input parameter with lifetime 'a, annotate the trait object's lifetime bound as Box<dyn Trait + 'a> (or Box<dyn Trait + '_>):
fn filter_numbers<'a>(
slice: &'a [i32],
only_positive: bool,
) -> Box<dyn Iterator<Item = i32> + 'a> {
if only_positive {
Box::new(slice.iter().copied().filter(|&x| x > 0))
} else {
Box::new(slice.iter().copied())
}
}
fn main() {
let nums = [-5, 10, -2, 20];
let pos: Vec<i32> = filter_numbers(&nums, true).collect();
let all: Vec<i32> = filter_numbers(&nums, false).collect();
println!("Positive only: {pos:?}");
println!("All numbers: {all:?}");
}
Positive only: [10, 20]
All numbers: [-5, 10, -2, 20]
-> impl Trait vs. -> Box<dyn Trait> Decision Guide
| Requirement | Use -> impl Trait |
Use -> Box<dyn Trait> |
|---|---|---|
| Function returns a single concrete type | Yes (zero heap allocation, inlinable) | Unnecessary overhead |
Function returns different types in if / else or match |
No (E0308) |
Yes |
Trait is not dyn-compatible (e.g. Clone) |
Yes | No (E0038) |