A trait defines functionality a particular type has and can share with other types. We can use traits to define shared behavior in an abstract way. We can use trait bounds to specify that a generic type can be any type that has certain behavior.
Traits are similar to a feature often called interfaces in other languages, although with some differences.
Trait definitions are a way to group method signatures together to define a set of behaviors necessary to accomplish some purpose.
We canβt implement external traits on external types. This restriction is part of a property called coherence, and more specifically the orphan rule, so named because the parent type is not present. This rule ensures that other peopleβs code canβt break your code and vice versa. Without the rule, two crates could implement the same trait for the same type, and Rust wouldnβt know which implementation to use.
Default implementations
Sometimes itβs useful to have default behavior for some or all of the methods in a trait instead of requiring implementations for all methods on every type. Then, as we implement the trait on a particular type, we can keep or override each methodβs default behavior.
To use a default implementation to summarize instances of NewsArticle
, we specify an empty impl
block with impl Summary for NewsArticle {}
.
Default implementations can call other methods in the same trait, even if those other methods donβt have a default implementation.
Note that it isnβt possible to call the default implementation from an overriding implementation of that same method.
Traits as Parameters
There are several ways to pass object that implements some trait as a parameter to a function or a method.
Instead of a concrete type for the item
parameter, we specify the impl
keyword and the trait name.
The impl Trait
syntax works for straightforward cases but is actually syntax sugar for a longer form known as a trait bound; it looks like this:
This longer form is equivalent to the example in the previous section but is more verbose.
The impl Trait
syntax is convenient and makes for more concise code in simple cases, while the fuller trait bound syntax can express more complexity in other cases.
We can also specify more than one trait bound.
or
Using too many trait bounds has its downsides. Each generic has its own trait bounds, so functions with multiple generic type parameters can contain lots of trait bound information between the functionβs name and its parameter list, making the function signature hard to read. For this reason, Rust has alternate syntax for specifying trait bounds inside a where
clause after the function signature. So instead of writing this:
we can use a where
clause, like this:
This functionβs signature is less cluttered: the function name, parameter list, and return type are close together, similar to a function without lots of trait bounds.
Returning types that implement Traits
We can also use the impl Trait
syntax in the return position to return a value of some type that implements a trait:
The ability to specify a return type only by the trait it implements is especially useful in the context of closures and iterators.
However, you can only use impl Trait
if youβre returning a single type. For example, this code that returns either a NewsArticle
or a Tweet
with the return type specified as impl Summary
wouldnβt work:
Returning either a NewsArticle
or a Tweet
isnβt allowed due to restrictions around how the impl Trait
syntax is implemented in the compiler.
Weβll cover how to write a function with this behavior in the βUsing Trait Objects That Allow for Values of Different Typesβ section of Chapter 17.
Using Trait Bounds to Conditionally Implement Methods
Pair<T>
only implements thecmp_display
method if its inner typeT
implements thePartialOrd
trait that enables comparison and theDisplay
trait that enables printing.
We can also conditionally implement a trait for any type that implements another trait. Implementations of a trait on any type that satisfies the trait bounds are called blanket implementations and are extensively used in the Rust standard library. For example, the standard library implements the ToString
trait on any type that implements the Display
trait. The impl
block in the standard library looks similar to this code:
Blanket implementations appear in the documentation for the trait in the βImplementorsβ section.