Traits

Defining Traits

A trait definition uses the trait keyword followed by an UpperCamelCase name and a braced body declaring the associated items that implementing types must provide or inherit.

Syntax and Associated Items

Inside a trait block, the keyword Self (with a capital S) is a built-in type alias representing whichever concrete type is implementing the trait. A trait definition can contain four kinds of associated items:

trait Codec {
    // 1. Associated constant (with or without a default value):
    const VERSION: u8 = 1;

    // 2. Associated type placeholder:
    type Output;

    // 3. Required method signature (terminated by a semicolon `;`):
    fn encode(&self) -> Self::Output;

    // 4. Associated function (no `self` parameter) or default method (with a `{ ... }` body):
    fn header_byte() -> u8 {
        Self::VERSION
    }
}

struct HexId(u16);

impl Codec for HexId {
    type Output = String;

    fn encode(&self) -> Self::Output {
        format!("{:04x}", self.0)
    }
}

fn main() {
    let id = HexId(255);
    println!("Version: {}, Header: {}, Encoded: {}", HexId::VERSION, HexId::header_byte(), id.encode());
}
Version: 1, Header: 1, Encoded: 00ff

Required vs. Provided Items

Each method or constant in a trait definition is either required (ends with ;) or provided (supplies a default { ... } body or = value expression):

Item Form Syntax inside trait Responsibility of impl Trait for Type
Required method fn summarize(&self) -> String; Must provide a concrete method body
Provided (default) method fn tag(&self) -> &str { "item" } May inherit the default or override it
Required associated constant const ID: u32; Must assign a constant value (const ID: u32 = 10;)
Provided associated constant const ID: u32 = 0; May inherit 0 or override it
Associated type type Item; Must specify a concrete type (type Item = u8;)

Visibility of Trait Definitions and Trait Items

A trait itself has a visibility modifier (pub trait, pub(crate) trait, or private trait). However, individual methods, associated types, and associated constants inside a trait never take pub keywords—every item in a trait automatically shares the visibility of the trait itself:

pub mod storage {
    pub trait Store {
        // Automatically public wherever `Store` is visible; writing `pub fn` is a syntax error:
        fn save(&mut self, key: &str, value: &str);
        fn load(&self, key: &str) -> Option<&str>;
    }
}

use std::collections::HashMap;
use storage::Store;

struct MemoryStore {
    map: HashMap<String, String>,
}

impl Store for MemoryStore {
    fn save(&mut self, key: &str, value: &str) {
        self.map.insert(key.to_owned(), value.to_owned());
    }

    fn load(&self, key: &str) -> Option<&str> {
        self.map.get(key).map(String::as_str)
    }
}

fn main() {
    let mut db = MemoryStore {
        map: HashMap::new(),
    };
    db.save("host", "127.0.0.1");
    println!("host = {:?}", db.load("host"));
}
host = Some("127.0.0.1")

Using Self in Parameters and Return Types

A trait definition can reference Self in parameter or return positions to express operations between two instances of the same implementing type or constructors returning an instance of the implementing type:

trait Mergeable {
    fn empty() -> Self;
    fn merge(&self, other: &Self) -> Self;
}

#[derive(Debug, PartialEq)]
struct Stats {
    requests: u64,
    errors: u64,
}

impl Mergeable for Stats {
    fn empty() -> Self {
        Self {
            requests: 0,
            errors: 0,
        }
    }

    fn merge(&self, other: &Self) -> Self {
        Self {
            requests: self.requests + other.requests,
            errors: self.errors + other.errors,
        }
    }
}

fn main() {
    let s1 = Stats {
        requests: 120,
        errors: 2,
    };
    let s2 = Stats {
        requests: 80,
        errors: 1,
    };
    let combined = s1.merge(&s2);
    println!("Combined: {combined:?}, Empty: {:?}", Stats::empty());
}
Combined: Stats { requests: 200, errors: 3 }, Empty: Stats { requests: 0, errors: 0 }