Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What does it mean to implement a trait for another trait?

Tags:

rust

traits

I read this answer but I'm still confused.

  1. 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?

  2. I can also declare impl B for &dyn A {}, so why I need impl B for dyn A {}? What's the use case?

  3. 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.

like image 655
Izana Avatar asked Jun 09 '20 00:06

Izana


People also ask

What is the point of traits in Rust?

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 is a blanket implementation?

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.

What is implement in Rust?

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.

How do you implement a trait?

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.


1 Answers

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”?

like image 162
Pandemonium Avatar answered Jan 03 '23 16:01

Pandemonium