Similar to struct, the definition for a trait type specifies named methods (and, possibly, fields). For example:
trait Id: id u32 fn print()
However, trait types accomplish a very different purpose than struct types. Instead of specifying the complete field layout and behavior of concrete data, trait types abstractly specify a common pattern of shared behavior (methods) and state (fields) across multiple concrete types. By this understanding, the Id trait above expresses the idea that there are other types out there which define a print method (that accepts no arguments and return no values) as well as an unsigned integer field named id.
Because traits abstract over other types, their use differs from concrete types (like structs) in two important ways:
- One cannot explicitly create a value whose type is a trait. Thus, it is not possible to use a constructor of this type (e.g., imm node = Id[12] is illegal). Instead, you create a concrete-type value, and then you view that concrete value through the lens of a compatible trait.
- A trait usually specifies methods that have no implementation logic, as the print method above demonstrates. When a method is called on a trait-based value, it will automatically substitute in the method implementation logic defined by the concrete value's type.
Traits make possible several valuable kinds of polymorphism, particularly:
- Trait-based variants, which enable definition of multiple variant structs that share some base trait's common fields and methods.
- Virtual reference interface, enabling the use of methods (and fields) on values whose concrete type can only be determined at runtime.
- Generic constraints, which can be use to restrict which parametric types can be successfully substituted in to instantiate (monomorphize) a generic type.