| |

Rust 41 🦀 Returning Types that Implement Traits (impl Trait)

A generic function declares its type parameters in angle brackets, and the caller chooses the concrete types. An impl Trait return type inverts that relationship: the function chooses the concrete type, and the caller is told only that the type implements a trait. The syntax is -> impl Trait, and it reads as “this function returns some type that implements Trait, but I am not going to name it.” The concrete type is known to the compiler, so monomorphization still applies, and there is no runtime dispatch. The type is simply hidden from the caller’s source code.

The need for this feature comes from types that cannot be named. A closure has a unique, compiler-generated type that has no name in the source language. An iterator chain returns a deeply nested type like Map<Filter<Chars<&str>, fn(&char) -> bool>, fn(char) -> usize> that is technically nameable but practically unreadable. Before impl Trait, the only ways to return such a value were to box it (Box<dyn Iterator<Item = T>>), which adds a heap allocation and dynamic dispatch, or to write the fully expanded type, which is unmaintainable. The impl Trait return type lets the function return the closure or the iterator chain directly, with the concrete type inferred by the compiler and hidden from the signature.

The feature has an important restriction: the caller cannot name the returned type. This means the caller cannot store it in a struct field, cannot use it as a type parameter, and cannot write the type in a function signature. The impl Trait type is opaque: the caller knows only that it implements the trait, and the trait’s methods are the only operations available. If the caller needs to name the type — to store it in a struct, to return it from another function, or to use it in a trait bound — the type must be either a concrete type or a boxed trait object.

This chapter covers three areas. First, why impl Trait return types exist — the unnameable-type problem and the limitations of the alternatives. Second, how impl Trait return types work — the syntax, the opacity guarantee, the single-concrete-type rule, and the interaction with generics. Third, when to use impl Trait versus dyn Trait versus a concrete type — the trade-offs between static dispatch, dynamic dispatch, and named types, and the patterns for returning iterators, closures, and futures. The chapter ends with a complete example session, a quick reference, best practices, common pitfalls, real-world examples, and diagrams showing the type-opacity model.

Key point: impl Trait in return position hides the concrete type while preserving static dispatch. The function chooses the concrete type, and the caller knows only that it implements the trait. The returned type cannot be named by the caller, cannot be stored in a struct field without boxing, and cannot vary between branches of the function. Use impl Trait when the concrete type is unnameable or unreadable; use dyn Trait when the caller needs to name the type or when the concrete type varies at runtime.


Why impl Trait return types exist

The unnameable-closure problem. A closure has a unique type that the compiler generates. The type has no name in the source language, and it cannot be written in a function signature. A function that returns a closure cannot write the return type. Before impl Trait, the only options were to box the closure (Box<dyn Fn(i32) -> i32>) or to return a function pointer (fn(i32) -> i32), which loses the captured environment. The impl Trait return type lets the function return the closure directly, with the concrete type inferred by the compiler and hidden from the signature.

The nested-iterator problem. An iterator chain produces a deeply nested type. The chain text.chars().filter(|c| c.is_alphabetic()).map(|c| c.to_uppercase()) has a type like Map<Filter<Chars<&str>, [closure]>, [closure]>. The type is technically nameable, but the name is unreadable and changes whenever the chain is modified. Writing the type in the function signature is impractical. The impl Trait return type lets the function write -> impl Iterator<Item = char>, and the compiler infers the concrete type.

The abstraction problem. A function’s signature is a contract. The caller needs to know what the function does, not how it is implemented. Returning the concrete type exposes the implementation: the caller can see that the function uses a Filter and a Map, which is an implementation detail. The impl Trait return type exposes only the trait, which is the abstraction. The caller can use the trait’s methods, and the implementation can change without changing the signature.

The static-dispatch problem. The alternative to impl Trait is often Box<dyn Trait>, which uses dynamic dispatch. The dyn trait object is a fat pointer: a pointer to the data plus a pointer to the vtable. Every method call goes through the vtable, which is a runtime lookup. The impl Trait return type uses static dispatch: the compiler knows the concrete type, and the method call is a direct call. There is no heap allocation and no vtable. The performance difference matters in hot loops and in iterator chains.

The single-type problem. The impl Trait return type promises a single concrete type. The compiler must be able to determine that type, and it must be the same type on every return path. A function that returns two different types depending on a condition cannot use impl Trait unless both types are the same, which is rarely the case. This restriction is what distinguishes impl Trait from dyn Trait: the dyn Trait return type can return any type that implements the trait, because the type is erased at runtime.

The trade-off. The impl Trait return type hides the concrete type, which is an advantage for abstraction and a disadvantage for the caller. The caller cannot name the type, so it cannot store it in a struct field, cannot use it as a generic parameter, and cannot write it in another function signature. The caller can only use the trait’s methods. The trade-off is between the abstraction and performance of impl Trait and the flexibility and nameability of a concrete type or a boxed trait object.


a. The syntax and the opacity guarantee

The impl Trait return type is written as -> impl Trait. The function returns some concrete type that implements Trait, and the compiler infers the type from the function body.

fn make_adder(x: i32) -> impl Fn(i32) -> i32 {
    move |y| x + y
}

The make_adder function returns a closure that captures x. The closure’s type is unnameable, so the return type is impl Fn(i32) -> i32. The caller can call the returned value as a function, because Fn is the trait, but the caller cannot name the closure’s type.

The opacity guarantee is the core of the feature. The caller knows that the returned value implements Trait, and nothing more. The concrete type is hidden, and the caller cannot rely on it. This means the function can change its implementation — returning a different concrete type that implements the same trait — without breaking the caller. The signature is the contract, and the implementation is free to change.

fn numbers() -> impl Iterator<Item = i32> {
    (1..=5).map(|n| n * 2)
}

The numbers function returns an iterator of i32. The caller can iterate over the result, but cannot name the type. The implementation can change from a Map to a Filter to a custom iterator, as long as the item type stays i32.

The opacity also means the caller cannot use the concrete type’s inherent methods. Only the trait’s methods are available. A function that returns impl Iterator<Item = i32> returns a value that has the Iterator methods (next, map, filter, collect, and the rest), but the concrete type’s inherent methods are not visible.


b. The single-concrete-type rule

The impl Trait return type promises a single concrete type. The compiler must be able to determine that type, and it must be the same type on every return path. A function that returns two different types is a compile error, even if both types implement the trait.

// ❌ ERROR: the two branches return different types
fn make_iterator(flag: bool) -> impl Iterator<Item = i32> {
    if flag {
        (1..=3).map(|n| n * 2)
    } else {
        (1..=3).filter(|n| n % 2 == 0)
    }
}

The first branch returns a Map iterator, and the second returns a Filter iterator. The two types are different, so the function does not compile. The error message is “if and else have incompatible types,” and the fix is to box the iterators or to use dyn Trait.

// ✅ Use Box<dyn Iterator> when the type varies
fn make_iterator(flag: bool) -> Box<dyn Iterator<Item = i32>> {
    if flag {
        Box::new((1..=3).map(|n| n * 2))
    } else {
        Box::new((1..=3).filter(|n| n % 2 == 0))
    }
}

The Box<dyn Iterator> erases the concrete type at runtime, so the two branches can return different types. The cost is a heap allocation and a vtable lookup per method call.

The single-type rule also applies to early returns and to match arms. Every return path must produce the same concrete type. If the function has a single return path, the rule is automatically satisfied.


c. impl Trait versus dyn Trait versus a concrete type

The three return-type strategies have different trade-offs. The choice depends on whether the concrete type is nameable, whether it varies at runtime, and whether the caller needs to name it.

impl Trait is the right choice when the concrete type is unnameable (a closure) or unreadable (a nested iterator), the type does not vary at runtime, and the caller does not need to name it. The dispatch is static, and there is no heap allocation.

dyn Trait (usually Box<dyn Trait>) is the right choice when the concrete type varies at runtime, or when the caller needs to name the type — for example, to store it in a struct field or to use it as a generic parameter. The dispatch is dynamic, and there is a heap allocation for the box and a vtable lookup for each method call.

A concrete type is the right choice when the type is nameable and the caller may need to use its inherent methods. The signature is more verbose, but the caller has full access to the type.

Aspectimpl TraitBox<dyn Trait>Concrete type
DispatchStaticDynamicStatic
Heap allocationNoYesNo
Type nameableNoYesYes
Type varies at runtimeNoYesNo
Inherent methodsNoNoYes
Use caseIterators, closuresHeterogeneous collections, pluginsNamed types

The iterator case is the most common. A function that returns an iterator chain uses impl Iterator<Item = T> because the chain’s type is unreadable. A function that returns a boxed iterator uses Box<dyn Iterator<Item = T>> because the chain’s type varies or because the caller needs to store it.

The closure case is similar. A function that returns a closure uses impl Fn(...) because the closure’s type is unnameable. A function that returns a boxed closure uses Box<dyn Fn(...)> because the closure’s type varies or because the caller needs to store it.

The Future case is the newest. An async fn returns impl Future<Output = T> implicitly, and the concrete type is the compiler-generated state machine. The type is unnameable, so impl Future is the only way to write the return type. The impl Trait return type is what makes async fn possible.


Complete Example Session

// ============================================
// PART 1: RETURNING A CLOSURE
// ============================================

fn make_adder(x: i32) -> impl Fn(i32) -> i32 {
    move |y| x + y
}

fn main() {
    let add_five = make_adder(5);
    println!("{}", add_five(10)); // 15
}

// The closure's type is unnameable.
// impl Fn(i32) -> i32 is the only way to write it.


// ============================================
// PART 2: RETURNING AN ITERATOR
// ============================================

fn evens(limit: u32) -> impl Iterator<Item = u32> {
    (1..=limit).filter(|n| n % 2 == 0)
}

fn main() {
    let result: Vec<u32> = evens(10).collect();
    println!("{:?}", result); // [2, 4, 6, 8, 10]
}

// The concrete type is Filter<RangeInclusive<u32>, [closure]>.
// impl Iterator<Item = u32> hides it.


// ============================================
// PART 3: CHAINING ITERATORS
// ============================================

fn process(text: &str) -> impl Iterator<Item = String> + '_ {
    text.lines()
        .filter(|line| !line.is_empty())
        .map(|line| line.trim().to_uppercase())
}

fn main() {
    let text = "hello\n\nworld\n  rust  \n";
    let result: Vec<String> = process(text).collect();
    println!("{:?}", result); // ["HELLO", "WORLD", "RUST"]
}

// The lifetime '_ is required because the iterator
// borrows from the input string.


// ============================================
// PART 4: THE SINGLE-TYPE RULE
// ============================================

// ❌ This does not compile:
// fn make_iterator(flag: bool) -> impl Iterator<Item = i32> {
//     if flag {
//         (1..=3).map(|n| n * 2)
//     } else {
//         (1..=3).filter(|n| n % 2 == 0)
//     }
// }

// ✅ The boxed version compiles:
fn make_iterator(flag: bool) -> Box<dyn Iterator<Item = i32>> {
    if flag {
        Box::new((1..=3).map(|n| n * 2))
    } else {
        Box::new((1..=3).filter(|n| n % 2 == 0))
    }
}

fn main() {
    let a: Vec<i32> = make_iterator(true).collect();
    let b: Vec<i32> = make_iterator(false).collect();
    println!("{:?} {:?}", a, b);
}


// ============================================
// PART 5: RETURNING A FUTURE
// ============================================

async fn fetch_data(url: &str) -> String {
    format!("data from {}", url)
}

// The return type is impl Future<Output = String>.
// The concrete type is the compiler-generated state machine.
// It is unnameable, so impl Future is the only way to write it.


// ============================================
// PART 6: A GENERIC FUNCTION WITH impl Trait
// ============================================

fn print_all(items: impl IntoIterator<Item = impl std::fmt::Display>) {
    for item in items {
        println!("{}", item);
    }
}

fn main() {
    print_all(vec![1, 2, 3]);
    print_all(vec!["a", "b", "c"]);
}

// impl Trait in argument position is syntactic sugar
// for a generic parameter.


// ============================================
// PART 7: COMBINING impl Trait WITH LIFETIMES
// ============================================

fn words<'a>(text: &'a str) -> impl Iterator<Item = &'a str> {
    text.split_whitespace()
}

fn main() {
    let text = String::from("hello world rust");
    let result: Vec<&str> = words(&text).collect();
    println!("{:?}", result); // ["hello", "world", "rust"]
}

// The lifetime 'a is required because the iterator
// borrows from the input string.


// ============================================
// PART 8: impl Trait VS dyn Trait
// ============================================

// impl Trait: static dispatch, no allocation
fn static_iter() -> impl Iterator<Item = i32> {
    (1..=3).map(|n| n * 2)
}

// dyn Trait: dynamic dispatch, heap allocation
fn dynamic_iter() -> Box<dyn Iterator<Item = i32>> {
    Box::new((1..=3).map(|n| n * 2))
}

// Use impl Trait when the type is known at compile time.
// Use Box<dyn Trait> when the type varies or must be named.


// ============================================
// PART 9: RETURNING A CUSTOM TRAIT
// ============================================

trait Animal {
    fn name(&self) -> &str;
    fn sound(&self) -> &str;
}

struct Dog;
impl Animal for Dog {
    fn name(&self) -> &str { "Dog" }
    fn sound(&self) -> &str { "Woof" }
}

fn make_animal() -> impl Animal {
    Dog
}

fn main() {
    let animal = make_animal();
    println!("{} says {}", animal.name(), animal.sound());
}

// The caller knows the return type implements Animal.
// The concrete type Dog is hidden.


// ============================================
// PART 10: THE COMPLETE EXAMPLE
// ============================================

use std::fmt::Display;

fn parse_numbers(input: &str) -> impl Iterator<Item = i32> + '_ {
    input
        .split(',')
        .filter_map(|s| s.trim().parse::<i32>().ok())
}

fn format_results<T: Display>(items: impl IntoIterator<Item = T>) -> String {
    items
        .into_iter()
        .map(|item| format!("[{}]", item))
        .collect::<Vec<_>>()
        .join(", ")
}

fn main() {
    let input = "1, 2, three, 4, five, 6";
    let numbers = parse_numbers(input);
    let formatted = format_results(numbers);
    println!("{}", formatted); // [1], [2], [4], [6]
}

// parse_numbers returns impl Iterator<Item = i32>.
// The concrete type is unnameable.
// format_results accepts any IntoIterator.

The ten parts show returning a closure, returning an iterator, chaining iterators, the single-type rule, returning a future, impl Trait in argument position, combining with lifetimes, impl Trait versus dyn Trait, returning a custom trait, and the complete example.


Quick Reference

The Syntax

FormMeaning
-> impl TraitReturns some type implementing Trait
-> impl Trait + '_With a lifetime bound
-> impl Trait + SendWith additional bounds
fn f(x: impl Trait)Argument position (generic sugar)

impl Trait vs dyn Trait

Aspectimpl TraitBox<dyn Trait>
DispatchStaticDynamic
Heap allocationNoYes
Type nameableNoYes
Type varies at runtimeNoYes
Inherent methodsNoNo
Use caseIterators, closuresHeterogeneous, plugins

Common Return Types

TraitUse case
impl Iterator<Item = T>Iterator chains
impl Fn(...) -> TClosures
impl Future<Output = T>Async functions
impl DisplayFormattable values
impl Trait (custom)Opaque custom types

The Rules

RuleMeaning
Single concrete typeAll return paths must return the same type
Caller cannot nameThe type is opaque
Static dispatchNo vtable, no heap allocation
Lifetime boundsRequired when the returned type borrows

Best Practices

✅ Do This:

// Use impl Trait for iterator chains
fn evens(limit: u32) -> impl Iterator<Item = u32> { ... }                 // ✅
// Use impl Trait for closures
fn make_adder(x: i32) -> impl Fn(i32) -> i32 { move |y| x + y }          // ✅
// Add lifetime bounds when borrowing
fn words<'a>(text: &'a str) -> impl Iterator<Item = &'a str> { ... }      // ✅
// Use Box<dyn Trait> when the type varies
fn make(flag: bool) -> Box<dyn Iterator<Item = i32>> { ... }              // ✅
// Use impl Trait in argument position
fn print_all(items: impl IntoIterator<Item = String>) { ... }             // ✅

❌ Don’t Do This:

// Don't return different types from different branches
fn make(flag: bool) -> impl Iterator<Item = i32> { if flag { ... } else { ... } } // ❌
// Don't try to name the return type
let x: Map<Filter<...>> = evens(10); // cannot name it                    // ❌
// Don't use impl Trait when the caller needs the concrete type
fn make() -> impl Animal { Dog } // caller cannot access Dog's methods    // ❌
// Don't forget the lifetime bound
fn words(text: &str) -> impl Iterator<Item = &str> { ... } // missing '_ // ❌
// Don't use impl Trait for a single named type
fn make() -> impl Animal { Dog } // use -> Dog if the type is nameable    // ✅

Common Pitfalls

PitfallWhy It HappensFix
“Incompatible types” in if/elseDifferent concrete typesUse Box<dyn Trait>
“Cannot name the type”The type is opaqueUse a concrete type or box
Lifetime errorMissing lifetime boundAdd + '_ or the explicit lifetime
“Cannot borrow”The returned value borrowsAdd the lifetime bound
impl Trait in a trait methodNot allowed before Rust 1.75Use an associated type or Box<dyn>
Performance regressionUsed Box<dyn> unnecessarilyUse impl Trait for static dispatch

Real-World Examples

1. Iterator Chain

fn evens(limit: u32) -> impl Iterator<Item = u32> {
    (1..=limit).filter(|n| n % 2 == 0)
}

2. Closure

fn make_adder(x: i32) -> impl Fn(i32) -> i32 {
    move |y| x + y
}

3. Future

async fn fetch() -> String { ... }

4. With Lifetime

fn words<'a>(text: &'a str) -> impl Iterator<Item = &'a str> {
    text.split_whitespace()
}

5. Custom Trait

fn make_animal() -> impl Animal { Dog }

6. Boxed for Variance

fn make(flag: bool) -> Box<dyn Iterator<Item = i32>> { ... }

7. Argument Position

fn print_all(items: impl IntoIterator<Item = String>) { ... }

8. Multiple Bounds

fn make() -> impl Iterator<Item = i32> + Send { ... }

9. Chained Transformation

fn process(text: &str) -> impl Iterator<Item = String> + '_ {
    text.lines().filter(|l| !l.is_empty()).map(|l| l.to_uppercase())
}

10. Complete Example

fn parse_numbers(input: &str) -> impl Iterator<Item = i32> + '_ {
    input.split(',').filter_map(|s| s.trim().parse().ok())
}

Visual

The Type-Opacity Model

┌──────────────────────────────────────────────────────────────┐
│  THE TYPE-OPACITY MODEL                                      │
│                                                              │
│  Function:                                                   │
│  ┌──────────────────────────────────────────────────────┐    │
│  │  fn evens(limit: u32) -> impl Iterator<Item = u32> { │    │
│  │    (1..=limit).filter(|n| n % 2 == 0)                │    │
│  │  }                                                   │    │
│  └──────────────────────────────────────────────────────┘    │
│    │                                                         │
│    ▼                                                         │
│  Compiler infers:                                            │
│  ┌──────────────────────────────────────────────────────┐    │
│  │  Filter<RangeInclusive<u32>, [closure]>              │    │
│  │                                                       │    │
│  │  The concrete type is known to the compiler.         │    │
│  │  It is hidden from the caller.                       │    │
│  └──────────────────────────────────────────────────────┘    │
│    │                                                         │
│    ▼                                                         │
│  Caller sees:                                                │
│  ┌──────────────────────────────────────────────────────┐    │
│  │  impl Iterator<Item = u32>                           │    │
│  │                                                       │    │
│  │  The caller can use the Iterator methods.            │    │
│  │  The caller cannot name the concrete type.           │    │
│  └──────────────────────────────────────────────────────┘    │
│                                                              │
│  The abstraction is the trait.                               │
│  The implementation is hidden.                               │
│  The dispatch is static.                                     │
│                                                              │
└──────────────────────────────────────────────────────────────┘

impl Trait vs dyn Trait

┌──────────────────────────────────────────────────────────────┐
│  impl Trait vs dyn Trait                                     │
│                                                              │
│  impl Trait:                                                 │
│  ┌──────────────────────────────────────────────────────┐    │
│  │  fn make() -> impl Iterator<Item = i32> {            │    │
│  │    (1..=3).map(|n| n * 2)                            │    │
│  │  }                                                   │    │
│  │                                                       │    │
│  │  Static dispatch. No vtable. No heap allocation.     │    │
│  │  The type is known at compile time.                  │    │
│  └──────────────────────────────────────────────────────┘    │
│                                                              │
│  Box<dyn Trait>:                                             │
│  ┌──────────────────────────────────────────────────────┐    │
│  │  fn make() -> Box<dyn Iterator<Item = i32>> {        │    │
│  │    Box::new((1..=3).map(|n| n * 2))                  │    │
│  │  }                                                   │    │
│  │                                                       │    │
│  │  Dynamic dispatch. Vtable lookup. Heap allocation.   │    │
│  │  The type is erased at runtime.                      │    │
│  └──────────────────────────────────────────────────────┘    │
│                                                              │
│  Use impl Trait for performance.                             │
│  Use Box<dyn Trait> for flexibility.                         │
│                                                              │
└──────────────────────────────────────────────────────────────┘

The Single-Type Rule

┌──────────────────────────────────────────────────────────────┐
│  THE SINGLE-TYPE RULE                                        │
│                                                              │
│  ❌ TWO DIFFERENT TYPES:                                      │
│  ┌──────────────────────────────────────────────────────┐    │
│  │  fn make(flag: bool) -> impl Iterator<Item = i32> {  │    │
│  │    if flag {                                         │    │
│  │      (1..=3).map(|n| n * 2)    ← Map type            │    │
│  │    } else {                                          │    │
│  │      (1..=3).filter(|n| n % 2 == 0) ← Filter type   │    │
│  │    }                                                 │    │
│  │  }                                                   │    │
│  │                                                       │    │
│  │  ERROR: incompatible types                           │    │
│  └──────────────────────────────────────────────────────┘    │
│                                                              │
│  ✅ BOXED, ONE TYPE:                                          │
│  ┌──────────────────────────────────────────────────────┐    │
│  │  fn make(flag: bool) -> Box<dyn Iterator<Item = i32>>│    │
│  │    if flag {                                         │    │
│  │      Box::new((1..=3).map(|n| n * 2))                │    │
│  │    } else {                                          │    │
│  │      Box::new((1..=3).filter(|n| n % 2 == 0))        │    │
│  │    }                                                 │    │
│  │  }                                                   │    │
│  │                                                       │    │
│  │  Both branches return Box<dyn Iterator>.             │    │
│  └──────────────────────────────────────────────────────┘    │
│                                                              │
└──────────────────────────────────────────────────────────────┘

Returning a Future

┌──────────────────────────────────────────────────────────────┐
│  RETURNING A FUTURE                                          │
│                                                              │
│  async fn fetch(url: &str) -> String {                       │
│    format!("data from {}", url)                              │
│  }                                                           │
│                                                              │
│  The compiler transforms this into:                          │
│  ┌──────────────────────────────────────────────────────┐    │
│  │  fn fetch(url: &str) -> impl Future<Output = String> │    │
│  │  + '_ {                                              │    │
│  │    // compiler-generated state machine               │    │
│  │  }                                                   │    │
│  └──────────────────────────────────────────────────────┘    │
│                                                              │
│  The concrete type is the state machine.                     │
│  It is unnameable.                                           │
│  impl Future is the only way to write the return type.       │
│                                                              │
│  This is what makes async fn possible.                       │
│                                                              │
└──────────────────────────────────────────────────────────────┘

Summary

ItemValue
impl Trait returnReturns some type implementing Trait
OpacityThe concrete type is hidden from the caller
DispatchStatic
Heap allocationNone
Single-type ruleAll return paths must return the same type
dyn Trait alternativeBox<dyn Trait> for runtime variance
Lifetime boundRequired when the returned type borrows
Common usesIterators, closures, futures, custom traits
Argument positionfn f(x: impl Trait) is generic sugar

Key takeaways:

  • impl Trait in return position hides the concrete type. The function returns some type that implements the trait, and the caller knows only the trait. The concrete type is inferred by the compiler and preserved for static dispatch.
  • The feature exists because some types cannot be named. A closure has a compiler-generated type, and an iterator chain has a deeply nested type. The impl Trait return type lets the function return these values directly, without boxing them.
  • The dispatch is static. Unlike Box<dyn Trait>, the impl Trait return type has no vtable and no heap allocation. The compiler knows the concrete type, and the method call is a direct call.
  • The single-type rule requires the same concrete type on every return path. A function that returns two different types cannot use impl Trait and must use Box<dyn Trait> instead.
  • The caller cannot name the returned type. This means the caller cannot store it in a struct field, cannot use it as a generic parameter, and cannot write it in another function signature. The caller can only use the trait’s methods.
  • Lifetime bounds are required when the returned type borrows. A function that returns an iterator over a borrowed string must add + '_ or the explicit lifetime to the return type.
  • impl Trait is used for iterators, closures, and futures. These are the three cases where the concrete type is unnameable or unreadable. The async fn syntax is built on impl Future, which is only possible because of the impl Trait return type.
  • Use Box<dyn Trait> when the concrete type varies at runtime. A function that returns different types depending on a condition must box them, because the impl Trait return type promises a single concrete type.

Remember: impl Trait in return position is the way to return a value whose concrete type is unnameable or unreadable. The function chooses the type, the compiler infers it, and the caller knows only the trait. The dispatch is static, the allocation is avoided, and the implementation can change without changing the signature. The feature is restricted by the single-type rule and the inability of the caller to name the returned type. Use impl Trait for iterators, closures, and futures; use Box<dyn Trait> when the type varies or must be named; use a concrete type when the type is nameable and the caller needs its inherent methods.



Stop using slow, ad-bloated tool sites! 🤮

🔎 Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)

⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free

🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!