There are 2 ways to provide methods for a Trait itself, Rustdoc distinguishes them by saying "provided methods" and impl dyn XXX
. For example:
trait Trait {
fn foo(&self) {
println!("Default implementation");
}
}
impl Trait {
fn bar(&self) {
println!("Anonymous implementation?");
}
}
I noticed it when I was reading the documentation of Rust's failure
crate.
What are the use cases for them? What are the differences?
The first snippet,
trait Trait {
fn foo(&self) {
println!("Default implementation");
}
}
implements a provided method on the trait. This method can be overridden by a trait implementation, but it does not have to be overridden.
The second snippet,
impl Trait {
fn bar(&self) {
println!("Anonymous implementation?");
}
}
implements an inherent method on a trait object of type dyn Trait
. Method implementations for dyn Trait
can only be called for trait objects, e.g. of type &dyn Trait
. They can't receive self
by value, since dyn Trait
does not have a size known at compile time, and they can't be called on concrete types implementing Trait
(including generic types with a Trait
bound).
The modern notation is to write impl dyn Trait
instead of impl Trait
, and in fact this notation was one of the motivating examples for the introduction of the dyn
keyword – the old syntax did not provide any clues as to what the semantics are, whereas the new syntax with the dyn
keyword hints at the fact that this impl is only used together with dynamic dispatch.
A trait object is a fat pointer to an object implementing Trait
, but the concrete type of the object is not necessarily known at compile time. The fat pointer contains a pointer to the object data, as well as a pointer to the virtual method table of the object type. The latter is used to dynamically dispatch to the correct trait implementation at runtime.
It is rather uncommon to use impl dyn Trait
. Generally it's only useful if you want to make use of some dynamic type information, like downcasting to the actual type. The only traits with inherent methods on trait objects in the standard library are Any
and Error
.
In short: one can be overridden, and the other cannot.
When you define a trait, you define items that implementations of the trait may (or have to) override:
trait Trait {
fn foo(&self) {
println!("Default implementation");
}
}
impl Trait for i64 {
fn foo(&self) {
println!("i64 implementation: {}", self);
}
}
On the other hand, using impl Trait
, you define inherent methods, which cannot be overridden:
impl Trait {
fn bar(&self) {
self.foo();
self.foo()
}
}
// Try:
impl Trait for i64 {
fn bar(&self) { ... } // error: bar cannot be overridden.
}
As a result, inherent traits methods act as the Template Method Pattern: they provide a canvas linking together one or multiple overridable method(s).
If you look at the failure
crate that you linked, the method Failure::find_root_cause()
states:
This is equivalent to iterating over
iter_causes()
and taking the last item.
You may consider those inherent methods to be convenience methods, methods providing an easy/intuitive interface for common tasks which can be accomplished manually... but are conveniently pre-defined.
Note: any inherent method could be implemented as a free function taking the trait as a first argument; however free functions cannot be called in method position.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With