Reputation: 3229
In the following example of passing a trait as a parameter, what's the need of sending impl
in the function signature?
I understand that traits are more generic types and not concrete types, but since the Rust compiler doesn't allow sharing names across structs and traits, why is there a need to provide impl
in the function signature to represent the type?
pub fn notify(item: impl Summary) {
println!("Breaking news! {}", item.summarize());
}
The documentation mentions that the above signature is just syntactic sugar for the below signature. Wouldn't it make sense to use trait Summary
instead of impl Summary
as impl
can also be used to define methods on structs?
pub fn notify<T: Summary>(item: T) {
println!("Breaking news! {}", item.summarize());
}
Is there any hidden concept around it that I'm missing?
Upvotes: 6
Views: 7249
Reputation: 30011
Contrary to languages such as Go or Java, Rust allows for both static and dynamic dispatch, and some syntax was required to let programmers choose between the two.
As dynamic dispatch must work on objects which might not be Sized
, you need a reference to use it. That is, you would use &dyn Trait
or Box<dyn Trait>
(note: for historical reasons, the dyn
keyword is not required, but modern Rust uses it). In C++, dynamic dispatch also requires a reference or pointer.
Static dispatch is not something Go or Java have. In C++, it works with templates and duck-typing. In Rust, it works with generics and traits, and its original syntax was:
fn some_function<T: Trait>(foo: T) { … }
Later, the following syntax was added to the language:
fn some_function(foo: impl Trait) { … }
which is equivalent to the above.
This syntax was originally invented to be used in return types, where there is no generic equivalent:
fn some_function() -> impl Trait { … }
This means that some_function
can return any single type that implements Trait
, but this type must be known at compile time. This has some performance benefits over returning Box<Trait>
for example. In C++, the closest equivalent would be returning auto
or decltype(auto)
.
The syntax in parameter position was added for symmetry.
You might wonder why not simply make the generics implicit and have:
fn some_function(foo: Trait) { … }
But this would be slightly confusing. Trait
by itself is not sized, and therefore cannot be used as a parameter, unless they are generic. This would make traits stand out in the realm of unsized types. For example, if (foo: Trait)
would work, you might wonder why (foo: str)
doesn't, but what would that one mean? There is also other problems with making generics implicit, for example, generics in traits make the trait non-object-safe.
Later, Rust will likely extend those existential types and allow this at a module level:
type Foo = impl Bar;
(which is currently allowed on nightly, guarded by the type_alias_impl_trait
feature)
Finally, you are asking why the syntax is impl Foo
, rather than trait Foo
. This reads well as "a type that implements Foo". The original RFC doesn't discuss alternative syntaxes much. Another RFC discusses the syntax more, in particular whether the syntax should have been any Foo
in parameter position, and some Foo
in return position. The syntax trait Foo
was never considered as far as I am aware.
Upvotes: 19