tezvyn:

Rust's `impl Trait`: Hiding Concrete Types

AI-drafted, machine-checkedSource: doc.rust-lang.orgintermediate

Rust's `impl Trait` specifies a type by its behavior, not its name. Use it in function arguments for cleaner generics (`fn f(x: impl Debug)`) or in return types to hide complex types like closures and iterators, avoiding heap allocation.

WHY IT EXISTS Rust needed a way to handle types that are complex or impossible to name, like closures or long iterator chains. The old way involved Box<dyn Trait>, which meant heap allocation and dynamic dispatch overhead. impl Trait was introduced to solve this by allowing functions to return concrete, statically-known types while hiding their complex definitions from the caller.

THE MENTAL MODEL Think of impl Trait as a "type placeholder." In a function argument, it's a promise from the caller: "I'll give you some concrete type that does this." In a return position, it's a promise from the function: "I'll give you back one specific concrete type that does this, but you don't need to know its exact name."

HOW IT WORKS impl Trait can be used in two places: arguments and return types.

In argument position, fn notify(item: impl Summary) is syntactic sugar for a generic function fn notify<T: Summary>(item: T). It simplifies the signature when the full generic syntax is overkill. The compiler still creates a specialized version of the function for each concrete type it's called with.

In return position, fn returns_closure() -> impl Fn(i32) -> i32 declares that the function returns some concrete type that implements the Fn trait. The compiler knows the exact type, so it can perform optimizations and static dispatch. The caller, however, only sees the trait interface. This is key: it's a single, concrete type, just an anonymous one.

WHEN TO USE IT First, use it in function arguments to make simple generic functions more readable. Second, use it in return positions to return closures or chained iterators. This is the most powerful use case, as their concrete types are often unnameable and impl Trait lets you return them without the performance penalty of boxing them (Box<dyn Trait>).

WHEN NOT TO USE IT Avoid impl Trait in arguments if the caller needs to be able to specify the type, for example parse::<u32>("5"). A full generic parameter <T: FromStr> is required for that. For return types, do not use impl Trait if you need to return different concrete types from different branches of the function. For that, you still need a trait object like Box<dyn Error>.

ONE CANONICAL EXAMPLE Returning a closure. Before impl Trait, you needed a boxed trait object: fn get_adder() -> Box<dyn Fn(i32) -> i32> { Box::new(|x| x + 1) }. This allocates memory on the heap. With impl Trait, the code is cleaner and more performant, avoiding allocation: fn get_adder() -> impl Fn(i32) -> i32 { |x| x + 1 }.

Read the original → doc.rust-lang.org

Get five bites like this every day.

Tezvyn delivers a daily feed of 60-second tech bites with quizzes to lock in what you learn.