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 }