A trait is analogous to an interface or protocol from other languages. It enables types to advertise that they use some common behaviour (methods).
All of Rust’s operations are defined with traits. E.g., aaddition (
defined as the
std::ops::Add trait. Operators are just syntactic sugar for
a + b =
Defining a Trait
Here’s an example of a trait that contains a
summarize method. Any type that
has this trait needs to implement such a method.
We can also provide a default implementation:
A type may implement a trait as follows:
To use a trait that has some default implementation (without overwriting it), we can do it as follows:
Or we could overwrite it, the same way as implementing a trait.
We can define methods for any type that implements some trait. It is similiar to extensions in C#.
Rust does it with the
ToString method, like this:
Thanks to it, any type that implement the
Display trait has the
Here’s a function that expects any object that implements a
or a full generic form:
Function Return Value
Functions can return types and specify just the trait of these types:
Some traits (in the standard library and third party) have sensible default
implementations and they can be implemented on a type just by adding an
Here are the ones from the standard library:
#[derive(Debug)] before struct’s definition makes that struct
printable in debug mode (
Another way to debug print is with the use of
PartialEq and Eq
PartialEq allows checking equality with the
Underneath there’s just the
eq method. The default implementation checks all
fields of a struct if they’re equal.
Eq trait has no methods.
PartialOrd and Ord
Allows comparisons with the
<= operators. It can only be
applied to types that implement
PartialEq as well.
In structs, all fields are checked.
Allows creation of deep copy of a value. Default implementation calls
on each field of the type. Cloning might involve copying heap data.
Allows copying a value on a stack. All types that implement
Copy must also
Allows creating some hash of an instance. The default implementation combines
hash() of all the fields of a struct.
Allows to create a default value for a type. It provides a
Trait Objects enable polymorphism.
Without trait objects:
components vector’s items must all be of the same type. If we want to have
a vector that may contain values of any type (that implements
Draw), we can
use trait object:
Now, we could apply the following code on top of that to make use of polymorphism:
This feature is a bit similar to what we can do in languages like JS (duck typing), but different in the sense that the existence of required methods is not done during runtime, and the code cannot panic due to some value not implementing a required trait. It’s safer. However, Rust still needs to perform dynamic dispatch to find the code of the method on values in runtime. This incurs some cost.
Trait objects may be used if in trait’s methods:
- The return type isn’t
- There are no generic type parameters.
Associated types allow traits to act a bit as if they were generic.
A real example from the standard library:
next method will return
Option<Item> and the
Item type is unknown
in the trait’s definition. The types that increment
Iterator have to specify
Item stands for:
Some traits might require other traits also to be implemented by the types that want to use it.
In the example above,
Display is the supertrait of