Traits

Traits are a way to define shared behavior that types (structs, enums, etc.) can implement. They are similar to interfaces or abstract classes in other languages.

Trait Syntax

pub trait Summary {
    /// Returns a string that summarizes the item.
    fn summarize(&self) -> String;
}

pub struct NewsArticle {
    pub headline: String,
    pub location: String,
    pub author: String,
    pub content: String,
}

// Implement the `Summary` trait on the `NewsArticle` type.
impl Summary for NewsArticle {
    fn summarize(&self) -> String {
        format!("{}, by {} ({})", self.headline, self.author, self.location)
    }
}

fn main() {
    let na = NewsArticle {
        headline: "headline".to_string(),
        location: "location".to_string(),
        author: "author".to_string(),
        content: "...".to_string(),
    };
    // Since the type implements the trait, we can call its method.
    println!("Summary: {}", na.summarize());
}

Trait methods are in scope only when their trait is.

Default Implementation

Traits can provide default implementations for their methods. This allows types that implement the trait to use the default implementation or override it with their own custom implementation.

/// A trait.
trait Summary {
    /// Method signature.
    /// Types that implement the `Summary` trait must implement a method with
    /// the exact signature.
    fn summarize_author(&self) -> String;

    /// Traits can also provide _default implementations_ for some or all of
    /// their methods or functions.
    /// - Default implementations have a body.
    /// - Types that implement the trait can override the default
    ///   implementations, if desired.
    fn summarize(&self) -> String {
        // Default implementations can call other methods in the same trait,
        // even if those other methods don't have a default implementation.
        // Here, the default implementation uses the `summarize_author` method.
        format!("(Read more from {}...)", self.summarize_author())
    }
}

/// Let's define a `Blog` struct...
struct Blog {
    author: String,
}

/// ...that implements the `Summary` trait.
impl Summary for Blog {
    /// All non-default-implemented methods must be implemented for the struct.
    fn summarize_author(&self) -> String {
        self.author.clone()
    }
}

fn main() {
    // Instantiate a Blog struct...
    let blog = Blog {
        author: "ferris".into(),
    };
    // ...and call the function implemented by default in the trait.
    println!("{}", blog.summarize());
    // Because we've implemented `summarize_author` for `Blog`, the `Summary`
    // trait has given us the behavior of the `summarize` method without
    // requiring us to write any more code.
}
// Adapted from https://doc.rust-lang.org/book/ch10-02-traits.html

Supertraits

A trait can require that implementing types also implement other traits. These are called supertraits.

use std::fmt;

// A supertrait is a trait that another trait depends on.
// In this case, `OutlinePrint` depends on `Display`.
// It means that any type that implements `OutlinePrint` must also implement
// `Display`.
//
// BEWARE: supertraits are NOT inheritance!
// They are only a constraint mechanism. When you define a trait with a
// supertrait, you're essentially saying, "Any type that implements this trait
// must also implement these other traits."  In inheritance, a subclass can
// inherit data (fields) and default implementations from its superclass. This
// is not true in Rust.

trait OutlinePrint: fmt::Display {
    fn outline_print(&self) {
        println!("* {} *", self);
        // We can use `println!` here,
        // because `self` is guaranteed to implement `Display`.
    }
}

/// Implement the `OutlinePrint` trait for `String`.
/// `String` already implements the `Display` supertrait. That would not work
/// otherwise.
impl OutlinePrint for String {}

fn main() {
    String::from("test").outline_print();
}

"Newtype" Pattern

Unlike interfaces in languages like Java, C# or Scala, new traits can be implemented for existing types.

/// A trait for types that can be hashed to a u64.
trait MyHash {
    /// Returns a hash of the object.
    fn myhash(&self) -> u64;
}

/// Implement the `MyHash` trait for `i64`.
impl MyHash for i64 {
    fn myhash(&self) -> u64 {
        *self as u64
    }
}

fn main() {
    let x = 1i64;
    // Since `i64` implements the `MyHash` trait, we can call its method.
    println!("{}", x.myhash());
}

One restriction to note is that we can implement a trait on a type only if at least one of the trait or the type is local to our crate. If neither are, use the newtype pattern.

The newtype pattern involves creating a new type (typically, a tuple struct with a single field) to wrap an existing type. This allows you to implement traits on the wrapper type, even if you don't own the original type or the trait.

use std::fmt;

/// Tuple struct wrapping the type we want to add a non-local trait to.
///
/// This is the 'newtype pattern'.
struct Wrapper(Vec<String>);

impl fmt::Display for Wrapper {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "[{}]", self.0.join(", "))
    }
}
// If you want the new type to have every method the inner type has,
// implement the `Deref` trait instead.

fn main() {
    println!(
        "{}",
        Wrapper(vec!["example".to_string(), "example 2".to_string()])
    );
}

Traits as Parameters

Traits can be used as parameters to functions, allowing the function to accept any type that implements the specified trait.

trait Summary {
    fn summarize(&self) -> String;
}

// Accepts any type that implements the specified trait.
// This is called "impl Trait" syntax.
fn notify(item: &impl Summary) {
    // Since `item` is guaranteed to implement `Summary`, we can call its
    // `summarize` method.
    println!("Breaking news! {}", item.summarize());
}

// Trait bound syntax (mostly equivalent).
// This is called "trait bound" syntax.
fn notify2<T: Summary>(item: &T) {
    println!("Breaking news! {}", item.summarize());
}

/// A struct that implements the `Summary` trait.
struct Article {
    txt: String,
}

impl Summary for Article {
    fn summarize(&self) -> String {
        self.txt.clone()
    }
}

fn main() {
    let a = Article {
        txt: String::from("Some text"),
    };
    notify(&a);
    notify2(&a);
}

Multiple Traits

use std::clone::Clone;
use std::fmt::Debug;

/// `a_function` takes a reference to a type that implements both `Debug` and
/// `Clone`. Note the `+`
fn a_function(item: &(impl Debug + Clone)) {
    println!("{:?}", item.clone());
}

/// `some_function` takes two references to types `T` and `U` that both must
/// implement `Debug` and `Clone`. Note the `+`
fn some_function<T, U>(_t: &T, _u: &U) -> i32
where
    T: Debug + Clone,
    U: Debug + Clone,
{
    42
}

#[derive(Debug, Clone)]
struct S;

fn main() {
    let s = S;
    // `S` implements both `Debug` and `Clone`, thus we can call `some_function`
    // with it.
    a_function(&s);
}

Return-position impl Trait

You can use impl Trait in the return type of a function to indicate that the function returns a type that implements a specific trait, without specifying the exact type.

This is useful when the exact type is complex or not relevant to the caller.

//! Rust allows to write `impl Trait` as the return type of functions (often
//! called "RPIT"). This means that the function returns "some type that
//! implements the Trait". This is commonly used to return closures, iterators,
//! and other types that are complex or impossible to write explicitly.

/// This function returns a closure that takes an i32 and returns an i32.
fn returns_closure() -> impl Fn(i32) -> i32 {
    |x| x + 1
}

fn main() {
    let f = returns_closure();
    println!("{}", f(1));
}

Generic Traits

Traits can be generic, meaning they can have type parameters. This allows you to define traits that work with different types.

/// A trait that can be implemented for different types.
trait Test<T> {
    /// A function that takes a generic type `T` as input.
    fn test(_t: T);
}

struct SomeStruct;

/// Implement the `Test` trait for a `struct`.
// Note that the `<>` brackets appear in two places.
impl<T> Test<T> for SomeStruct {
    fn test(_t: T) {
        println!("This is the test function.");
    }
}

fn main() {
    SomeStruct::test(1);
    SomeStruct::test(true);
}

Associated Types

Traits can have associated types, which are types that are associated with the trait and can be used in its methods.

/// A trait that represents an iterator.
trait Iterator {
    /// The type of the elements yielded by the iterator.
    type Item;

    /// Returns the next element in the iterator, or `None` if the iterator is
    /// exhausted. Note the use of `::` to refer to the associated type.
    fn next(&mut self) -> Option<Self::Item>;
}

struct MyIterator(u32);

// We implement the trait for a given struct
impl Iterator for MyIterator {
    // ...and define what associated type should be used here
    type Item = u32;

    fn next(&mut self) -> Option<Self::Item> {
        Some(self.0)
    }
}

fn use_iterator(it: &mut impl Iterator<Item = u32>) -> Option<u32> {
    it.next()
}

/// A trait that represents the ability to add two values together.
///
/// A common pattern is a generic type (with a default) and an associated type.
trait Add<Rhs = Self> {
    /// The type of the result of the addition.
    type Output;

    /// Adds two values together.
    fn add(self, rhs: Rhs) -> Self::Output;
}

fn main() {
    let mut it = MyIterator(42);
    println!("{:?}", use_iterator(&mut it));
}

Trait Bounds

use std::collections::hash_map::DefaultHasher;
use std::hash::Hash;
use std::hash::Hasher;

/// Trait bounds allow you to specify that a generic type must implement a
/// certain trait.
///
/// Here, the `print_hash` function is generic over an unknown
/// type `T`, but requires that `T` implements the `Hash` trait.
fn print_hash<T: Hash>(t: &T) {
    let mut hasher = DefaultHasher::new();
    t.hash(&mut hasher);
    // The `finish` method returns the computed hash.
    println!("The hash is {:x}", hasher.finish());
}

/// This struct represents a pair of values.
///
/// It is generic over the types of the two values.
struct Pair<A, B> {
    first: A,
    second: B,
}

// Generics make it possible to implement a trait conditionally.
// Here, the `Pair` type implements `Hash` if, and only if,
// its components do.
impl<A: Hash, B: Hash> Hash for Pair<A, B> {
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.first.hash(state);
        self.second.hash(state);
    }
}

fn main() {
    let p = Pair {
        first: 1,
        second: "2",
    };
    print_hash(&p);
}

Constants in Traits

Traits can define constants that implementing types can use.

/// A trait demonstrating the use of associated constants.
trait Example {
    /// An associated constant without a default value.
    ///
    /// Implementors of this trait must provide a value for this constant.
    const CONST_NO_DEFAULT: i32;

    /// An associated constant with a default value.
    ///
    /// Implementors of this trait can optionally override this default.
    const CONST_WITH_DEFAULT: i32 = 99;
}
struct S;

impl Example for S {
    const CONST_NO_DEFAULT: i32 = 0;
}

fn main() {
    println!("{} {}", S::CONST_NO_DEFAULT, S::CONST_WITH_DEFAULT);
}

Async and Traits

See Async⮳.

See Also

Traits (blog)⮳.