In this lesson, you will learn how to add trait bounds to impl Trait return types to ensure your functions return objects that satisfy specific requirements without revealing their exact type.
The Problem with Opaque Types
When you use impl Trait as a return type, you are using an "opaque" type. The compiler knows what is being returned, but the caller only knows that the type implements the specified trait. Sometimes, however, you need to guarantee more. For example, if you are returning an iterator or a collection, you might want to ensure the result is also Send or Clone.
Adding Bounds to impl Trait
To constrain an opaque return type, you can use the + syntax directly within the function signature. This tells the compiler: "Return a type that implements this trait and satisfies these additional bounds."
Consider a scenario where you want to return a processor that can be sent between threads:
fn get_processor() -> impl Fn() + Send {
let data = String::from("Processing...");
move || println!("{}", data)
}
By adding + Send, you inform the compiler that the closure must be safe to move across thread boundaries. If the logic inside your function were to capture something that wasn't Send, the compiler would throw an error right at the function definition.
Why This Matters for Evolution
This technique is powerful because it allows you to evolve your internal logic without breaking your API. Because you haven't named the specific type (like a complex closure or an iterator chain), you can change your implementation—for example, switching from a Vec iterator to a HashMap iterator—as long as the new type still satisfies the bounds you promised (impl Iterator + Clone).
You keep your implementation private while still providing a robust contract to your users. They don't need to know exactly what the type is, only that it is capable of performing the specific behaviors you have promised through your trait bounds.
Key Takeaway: Combining impl Trait with bounds allows you to export an opaque type while strictly enforcing functional requirements, keeping your internal implementation details hidden and flexible.