impl Trait
isn't itself a type per se. It's used where types are also used in two places in stable Rust.
Argument position:
// Argument Position Impl Trait (APIT)
fn foo(s: impl Summary) { /* ... */ }
// Basically the same as the following, but you can't name the generic
fn foo<S: Summary>(s: impl Summary) { /* ... */ }
So APIT is basically like a generic parameter with a trait bound. You can pass in anything that meets the bound. Once you have implemented the trait:
impl Summary for Tweet {}
You can meet the : Summary
bound. So you could pass a Tweet
to foo
. Any other type that implements Summary
can be passed into foo
, as well.
Return position:
// Return Position Impl Trait (RPIT):
fn bar() -> impl Summary { /* ... */ }
This functionality is more unique. It says, "bar
is going to return some type that implements Summary
-- but that's all the caller gets to know. The type is otherwise opaque." It's useful for a few reasons:
- It's flexible for the writer of
foo
-- they can change the returned type without breaking downstream
- There are types in Rust which are unnameable, such as
- Closures
- Compiler-generated
Future
s
- APIT argument types
Because Tweet
implements Summary
, you could return a Tweet
from bar
.
However, note that an RPIT type is still an opaque alias for a single type. That is, you can't do something like
fn bar() -> impl Summary {
if random() {
Tweet::new("...");
} else {
Magazine::new("...");
}
}
Rust is statically typed, so you can't have a "sometimes This
, sometimes That
" return type.
(Your RPIT can vary in type based on generic input types, though. e.g. fn quz<T>(v: Vec<T>) -> impl Iterator<Item=T>
may return a different iterator type for every distinct T
.)
So here RPIT is an opaque alias, not a type itself. You can't add methods or implement other traits for a return-position impl Trait
, for example.
What if you do want to return different types that implement Summary
, or otherwise treat multiple types that implement Summary
as the same type? If you're trait is object safe (required for technical reasons), you can coerce any implementor of Summary
into dyn Summary
.
When you do this, the base type is erased. Instead you now have a dyn Summary
, which is its own singular, distinct, concrete type. The compiler also supplies an implementation of Summary
for dyn Summary
. This type is dynamically sized (the runtime size is the size of whatever base type got erased), so it has no statically known size -- we say it is not Sized
or that it is unsized. To be able to use unsized types, they have to be behind some sort of pointer, so you will mainly see things like Box<dyn Summary>
for owned dyn
objects and &dyn Summary
for borrowed ones.
If you "own" the trait, you own the dyn Trait
type too, and you can implement methods on it directly, implement other traits for it, etc.
That's a very brief introduction, but dyn Summary
is the closest thing to an impl Summary
that's actually type (for any implementor of Summary
).