Rust impl Trait in Depth
impl Trait is shorthand for a hidden generic parameter. The shorthand can describe a value coming into a function or a value going out.
Two impl parameters, two types allowed
Each impl Display is a separate hidden parameter, so a number and a word can arrive together:
fn show_pair(a: impl Display, b: impl Display) {
println!("pair: {a} and {b}");
}
show_pair(7, "seven");When the two must match, name the letter
One T covering both parameters is a promise impl Trait cannot make. The two arguments must now be the same type. And same_type(3, "four") would be refused.
fn same_type<T: Display>(a: T, b: T) {
println!("same: {a} and {b}");
}
same_type(3, 4);Promise more with +
The caller of first_two knows two facts about the hidden type: it iterates over i32, and it can be cloned. Cloning the iterator before collecting lets both copies run:
fn first_two() -> impl Iterator<Item = i32> + Clone {
vec![5, 6, 7, 8].into_iter().take(2)
}
let it = first_two();
let copy = it.clone();
println!("first run: {:?}", it.collect::<Vec<i32>>());
println!("clone run: {:?}", copy.collect::<Vec<i32>>());impl Trait inside a trait
A trait may promise that a method returns some iterator, without naming its type. Each implementor chooses its own one concrete iterator type:
trait Playlist {
fn titles(&self) -> impl Iterator<Item = String>;
}
struct Mix {
songs: Vec<String>,
}
impl Playlist for Mix {
fn titles(&self) -> impl Iterator<Item = String> {
self.songs.clone().into_iter()
}
}Example
use std::fmt::Display;
fn show_pair(a: impl Display, b: impl Display) {
println!("pair: {a} and {b}");
}
fn same_type<T: Display>(a: T, b: T) {
println!("same: {a} and {b}");
}
fn first_two() -> impl Iterator<Item = i32> + Clone {
vec![5, 6, 7, 8].into_iter().take(2)
}
trait Playlist {
fn titles(&self) -> impl Iterator<Item = String>;
}
struct Mix {
songs: Vec<String>,
}
impl Playlist for Mix {
fn titles(&self) -> impl Iterator<Item = String> {
self.songs.clone().into_iter()
}
}
fn main() {
show_pair(7, "seven");
same_type(3, 4);
let it = first_two();
let copy = it.clone();
println!("first run: {:?}", it.collect::<Vec<i32>>());
println!("clone run: {:?}", copy.collect::<Vec<i32>>());
let mix = Mix { songs: vec![String::from("intro"), String::from("finale")] };
for title in mix.titles() {
println!("track: {title}");
}
}Key points
- Every impl Trait parameter is its own secret generic parameter.
- Two of them in one signature may be filled by two different types
- impl Trait cannot say "the same type as that other parameter".
- When two positions must agree, name a generic instead.
- Write fn f<T>(a: T, b: T)
- Stack extra promises with +: -> impl Iterator<Item = i32> + Clone returns an iterator the caller may also clone
- Since Rust 1.75, a trait method may return impl Trait too.
- Each implementor still picks exactly one concrete return type
- A method returning impl Trait cannot be called through a trait object.
- dyn needs a return type it can name.
- So such traits stay static-only (awareness)
- It never mixes types.
- The moment two different concrete types must flow through one place, that place needs dyn instead.
In this example, four impl Trait facts appear in one program. show_pair takes a number and a word in one call, because each impl Display is its own hidden parameter. same_type uses one named T instead, so both arguments must share a type — 3 and 4 do. first_two returns an iterator with two promises: Item = i32 and Clone. main clones it. Both copies collect [5, 6]. Last, the Playlist trait itself returns impl Iterator. Mix answers with a vector iterator over its cloned songs. The loop prints the two track names.
Note
impl Trait hides a type.
Try it Yourself
Edit the code, then press Run.
Running is simulated · Rust Playground
Exercise
In fn join(a: impl Display, b: impl Display), may the two arguments have different types?