Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Differences between 2 styles of default implementations in a trait?

Tags:

rust

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?

like image 322
seamlik Avatar asked Jan 25 '19 19:01

seamlik


2 Answers

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.

like image 112
Sven Marnach Avatar answered Nov 01 '22 09:11

Sven Marnach


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.

like image 35
Matthieu M. Avatar answered Nov 01 '22 10:11

Matthieu M.