A trait describes what a type can do. Think of it as a qualification: different types can have the same qualification without becoming the same type. When a type implements a trait, it provides the trait’s methods, so code that requires that trait can rely on those methods being available. section 7.1 section 7.6
There are two common ways to give a type a trait:
- Use an attribute such as `#[derive(Debug)]` when Rust can generate the implementation. section 7.1
- Write `impl TraitName for TypeName` when the behavior needs your decisions. For example, Rust cannot guess what adding two values of your own struct should mean. section 7.1
A trait method can have a body, which supplies default behavior, or only a signature, which specifies its name, inputs, and return type. A type implementing a signature-only method must write its own body. Choose a default when most types should behave alike; require an implementation when their behavior will differ. section 7.1 section 7.1.1
use std::fmt;
struct Cat {
name: String,
age: u8,
}
impl fmt::Display for Cat {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} is a cat who is {} years old", self.name, self.age)
}
}
fn main() {
let mr_mantle = Cat {
name: "Reggie Mantle".to_string(),
age: 4,
};
println!("{mr_mantle}");
}
Here, `Cat` implements `Display` by writing its own `fmt` method. That lets `println!` print a `Cat` with `{}`. The unfamiliar formatter types can wait; the key step is supplying the behavior that `Display` requires. section 7.1.1
Compiler feedback can point you to a missing trait. If `{}` printing says a type does not implement `Display`, try `{:?}` for `Debug` printing if that type supports it. For an unfamiliar trait, check its documentation for “Required Methods”: those are the methods you will usually need to write. Traits also work as bounds on generic functions, letting a function accept different types that share the capabilities it needs. section 1.8 section 13.1.4 section 5.3



