Keyword trait

source ·
Expand description

A common interface for a group of types.

A trait is like an interface that data types can implement. When a type implements a trait it can be treated abstractly as that trait using generics or trait objects.

Traits can be made up of three varieties of associated items:

  • functions and methods
  • types
  • constants

Traits may also contain additional type parameters. Those type parameters or the trait itself can be constrained by other traits.

Traits can serve as markers or carry other logical semantics that aren’t expressed through their items. When a type implements that trait it is promising to uphold its contract. Send and Sync are two such marker traits present in the standard library.

See the Reference for a lot more information on traits.

Examples

Traits are declared using the trait keyword. Types can implement them using impl Trait for Type:

trait Zero {
    const ZERO: Self;
    fn is_zero(&self) -> bool;
}

impl Zero for i32 {
    const ZERO: Self = 0;

    fn is_zero(&self) -> bool {
        *self == Self::ZERO
    }
}

assert_eq!(i32::ZERO, 0);
assert!(i32::ZERO.is_zero());
assert!(!4.is_zero());
Run

With an associated type:

trait Builder {
    type Built;

    fn build(&self) -> Self::Built;
}
Run

Traits can be generic, with constraints or without:

trait MaybeFrom<T> {
    fn maybe_from(value: T) -> Option<Self>
    where
        Self: Sized;
}
Run

Traits can build upon the requirements of other traits. In the example below Iterator is a supertrait and ThreeIterator is a subtrait:

trait ThreeIterator: std::iter::Iterator {
    fn next_three(&mut self) -> Option<[Self::Item; 3]>;
}
Run

Traits can be used in functions, as parameters:

fn debug_iter<I: Iterator>(it: I) where I::Item: std::fmt::Debug {
    for elem in it {
        println!("{elem:#?}");
    }
}

// u8_len_1, u8_len_2 and u8_len_3 are equivalent

fn u8_len_1(val: impl Into<Vec<u8>>) -> usize {
    val.into().len()
}

fn u8_len_2<T: Into<Vec<u8>>>(val: T) -> usize {
    val.into().len()
}

fn u8_len_3<T>(val: T) -> usize
where
    T: Into<Vec<u8>>,
{
    val.into().len()
}
Run

Or as return types:

fn from_zero_to(v: u8) -> impl Iterator<Item = u8> {
    (0..v).into_iter()
}
Run

The use of the impl keyword in this position allows the function writer to hide the concrete type as an implementation detail which can change without breaking user’s code.

Trait objects

A trait object is an opaque value of another type that implements a set of traits. A trait object implements all specified traits as well as their supertraits (if any).

The syntax is the following: dyn BaseTrait + AutoTrait1 + ... AutoTraitN. Only one BaseTrait can be used so this will not compile:

trait A {}
trait B {}

let _: Box<dyn A + B>;
Run

Neither will this, which is a syntax error:

trait A {}
trait B {}

let _: Box<dyn A + dyn B>;
Run

On the other hand, this is correct:

trait A {}

let _: Box<dyn A + Send + Sync>;
Run

The Reference has more information about trait objects, their limitations and the differences between editions.

Unsafe traits

Some traits may be unsafe to implement. Using the unsafe keyword in front of the trait’s declaration is used to mark this:

unsafe trait UnsafeTrait {}

unsafe impl UnsafeTrait for i32 {}
Run

Differences between the 2015 and 2018 editions

In the 2015 edition the parameters pattern was not needed for traits:

trait Tr {
    fn f(i32);
}
Run

This behavior is no longer valid in edition 2018.