I read this answer but I'm still confused.
How do you interpret impl B for dyn A {}
?
trait A {
fn method_a(&self) {
println!("a");
}
}
trait B {
fn method_b(&self) {
println!("b")
}
}
impl B for dyn A {}
impl A for i32 {}
fn main() {
let x: &dyn A = &10;
x.method_b();
}
Playground
I can understand impl A for i32 {}
because i32
is a concrete type. dyn A
is not a concrete type (unsized, can't pass by value), and you cannot declare a dyn A
but you can only declare a &dyn A
. Should I interpret
// x.method_b();
(*x).method_b();
as *x
is dyn A
?
I can also declare impl B for &dyn A {}
, so why I need impl B for dyn A {}
? What's the use case?
Follow up: If I modify the code
fn main() {
let x: &dyn A = &10;
// have a B trait object over dyn A since
// dyn A implements B
let y: &dyn B = x;
}
It will fail and complain &dyn A
is not &dyn B
. I understand this is a reasonable complain but I provide the option for compiler to use impl B for dyn A {}
. Apparently, the compiler doesn't consider that's an option.
A trait tells the Rust compiler about functionality a particular type has and can share with other types. Traits are an abstract definition of shared behavior amongst different types. So, we can say that traits are to Rust what interfaces are to Java or abstract classes are to C++.
What are blanket implementations? Blanket implementations leverage Rust's ability to use generic parameters. They can be used to define shared behavior using traits. This is a great way to remove redundancy in code by reducing the need to repeat the code for different types with similar functionality.
An implementation is an item that associates items with an implementing type. Implementations are defined with the keyword impl and contain functions that belong to an instance of the type that is being implemented or to the type statically. There are two types of implementations: inherent implementations.
To implement a trait, declare an impl block for the type you want to implement the trait for. The syntax is impl <trait> for <type> . You'll need to implement all the methods that don't have default implementations.
You can't declare dyn A
but you can declare &dyn A
because dyn A
is a trait object type while &dyn A
is a pointer to an instance of type T
that implements A
.
Historically, a trait could be used as a type and a trait. For instance, these both work:
// Where B and A are traits
impl B for A {}
impl B for dyn A {}
Basically, dyn A
is just a sugar over A
to make it clearer that it is meant to be used as a trait object type. You don't implement a trait for another trait. You implement a trait for another trait object type.
&dyn A
is a pointer instance to an instance of type T
that implements A
and a virtual method table (vtable), which contains all the baggage of methods of A
that T
implements. This vtable lookup is necessary when an instance of type T
later calls A
's implementation at runtime.
Therefore, dyn A
is an unsized type while &dyn A
is a pointer with a known size.
Trait object of type dyn A
must be cast from a pointer to be used as a concrete type that implements A
. For example, in the code example, i32
can be cast to a dyn A
:
impl B for dyn A {}
impl A for i32 {}
fn main() {
let x: i32 = 10;
(&x as &dyn A).method_a();
(&x as &dyn A).method_b();
}
or it can be coerced by a function:
fn dispatch(a: &dyn A) {
a.method_b();
}
Because traits are dynamically sized types (DSTs), to use them as trait objects, we must put them behind some kind of pointer, like &dyn A
or Box<dyn A>
so it can point to a variable-sized value and access the vtable to call the implemented methods.
See also: What makes something a “trait object”?
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