Working Code
In Lesson 12 you put #[derive(Debug)] on structs. Back then, the explanation was "add this to print with {:?}." Now it's time to reveal what it really is — a trait!
struct Cat {
name: String,
age: u8,
}
struct Dog {
name: String,
age: u8,
}
trait Greet {
fn hello(&self) -> String;
}
impl Greet for Cat {
fn hello(&self) -> String {
format!("{} says meow!", self.name)
}
}
impl Greet for Dog {
fn hello(&self) -> String {
format!("{} says woof!", self.name)
}
}
fn main() {
let cat = Cat { name: "Whiskers".into(), age: 3 };
let dog = Dog { name: "Buddy".into(), age: 5 };
println!("{}", cat.hello());
println!("{}", dog.hello());
}
Greet is a trait. It means "implement this method and you earn the ability to greet." Cat and Dog greet differently, but both have the Greet certificate!
Try It Yourself
- Create a
Birdstruct and implement theGreettrait for it. - Call
hello()and verify the result. - Add a
goodbye(&self) -> Stringmethod to theGreettrait. What happens? (Hint: every implementor now shows an error!)
"Why?" — The Certificate Analogy
Think of traits as certificates:
Displaycertificate -> can useprintln!("{}", x)Debugcertificate -> can useprintln!("{:?}", x)Clonecertificate -> can use.clone()PartialEqcertificate -> can compare with==
No certificate? The compiler tells you: "This type doesn't have the X certificate."
Display — Show It to Users
To print with {}, you need the Display trait. It can't be auto-generated, so you implement it manually:
use std::fmt;
struct Point {
x: f64,
y: f64,
}
impl fmt::Display for Point {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "({}, {})", self.x, self.y)
}
}
fn main() {
let p = Point { x: 3.0, y: 4.0 };
println!("Coordinates: {p}"); // Coordinates: (3, 4)
// println!("{:?}", p); // error! no Debug
}
Debug — For Developers
To print with {:?}, you need the Debug trait. It can be auto-generated with derive:
#[derive(Debug)]
struct Point {
x: f64,
y: f64,
}
fn main() {
let p = Point { x: 3.0, y: 4.0 };
println!("{:?}", p); // Point { x: 3.0, y: 4.0 }
println!("{:#?}", p); // pretty-printed
}
Before / After — With and Without Display
Before — no Display
struct Temperature {
celsius: f64,
}
fn main() {
let t = Temperature { celsius: 36.5 };
// println!("{t}"); // error! `Temperature` doesn't implement `Display`
println!("Temp: {}°C", t.celsius); // must access the field directly
}
After — Display implemented
use std::fmt;
struct Temperature {
celsius: f64,
}
impl fmt::Display for Temperature {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:.1}°C", self.celsius)
}
}
fn main() {
let t = Temperature { celsius: 36.5 };
println!("Current temp: {t}"); // Current temp: 36.5°C
}
derive Macros — Auto-Certificates
Some traits can be auto-generated with #[derive(...)]. You already used this in Lesson 12!
| Trait | Ability | Example |
| ----------- | ------------------------- | --------------------- |
| Debug | Print with {:?} | println!("{:?}", x) |
| Clone | Duplicate with .clone() | let y = x.clone() |
| PartialEq | Compare with == | if a == b |
| Default | Create default values | Point::default() |
#[derive(Debug, Clone, PartialEq)]
struct Color {
r: u8,
g: u8,
b: u8,
}
fn main() {
let red = Color { r: 255, g: 0, b: 0 };
let red2 = red.clone(); // possible thanks to Clone
println!("{:?}", red); // possible thanks to Debug
println!("{}", red == red2); // possible thanks to PartialEq -> true
}
Why could you use .clone() back in Phase 1? That was the Clone trait at work!
Trait Bounds — "Only Types With This Certificate"
You can restrict a function to accept only types that implement certain traits:
use std::fmt::Display;
fn print_twice<T: Display>(item: T) {
println!("1st: {item}");
println!("2nd: {item}");
}
fn main() {
print_twice("hello");
print_twice(42);
// print_twice(vec![1, 2, 3]); // error! Vec doesn't implement Display
}
T: Display means "T must have the Display certificate." Generics and trait bounds are covered further in the next lesson.
Default Implementations — Preset Behavior
You can provide a default implementation in a trait. Types only override it when they need to:
trait Describable {
fn name(&self) -> &str;
// Default implementation — used if not overridden
fn describe(&self) -> String {
format!("Name: {}", self.name())
}
}
struct Product {
name: String,
price: u32,
}
impl Describable for Product {
fn name(&self) -> &str {
&self.name
}
// describe() is not overridden, so the default is used
}
struct PremiumProduct {
name: String,
price: u32,
}
impl Describable for PremiumProduct {
fn name(&self) -> &str {
&self.name
}
// Override the default implementation!
fn describe(&self) -> String {
format!("* {} (${}.00)", self.name(), self.price)
}
}
fn main() {
let apple = Product { name: "Apple".into(), price: 1 };
let gold_apple = PremiumProduct { name: "Gold Apple".into(), price: 50 };
println!("{}", apple.describe()); // Name: Apple
println!("{}", gold_apple.describe()); // * Gold Apple ($50.00)
}
Exercise 1 (easy). Implement the Display trait for the Rectangle struct so it prints as "3 x 5 rectangle".
use std::fmt;
struct Rectangle {
width: u32,
height: u32,
}
// Implement Display here
fn main() {
let rect = Rectangle { width: 3, height: 5 };
println!("{rect}"); // "3 x 5 rectangle"
}
Exercise 2 (medium). Define a Summary trait and implement it for Article and Tweet.
// Define the Summary trait
// - Requires a summarize(&self) -> String method
struct Article {
title: String,
content: String,
}
struct Tweet {
username: String,
text: String,
}
// Implement Summary for each struct
// Article: "{title} — {first 20 chars of content}..."
// Tweet: "@{username}: {text}"
fn main() {
let article = Article {
title: "Rust 2024".into(),
content: "Rust has been voted the most loved language again this year.".into(),
};
let tweet = Tweet {
username: "rustlang".into(),
text: "Rust 1.80 released!".into(),
};
println!("{}", article.summarize());
println!("{}", tweet.summarize());
}
Exercise 3 (challenge). Use trait bounds to write a print_max function that accepts types implementing both Display and PartialOrd.
use std::fmt::Display;
fn print_max<T: Display + PartialOrd>(a: T, b: T) {
// Print the larger of a and b
// Hint: if a >= b { ... } else { ... }
}
fn main() {
print_max(10, 20); // Max: 20
print_max("apple", "banana"); // Max: banana
}
Q1. What is a trait?
- A) A way to define struct fields
- B) A way to define a common interface that types must implement
- C) A way to restrict function return types
- D) A way to specify variable lifetimes
Q2. What does #[derive(Debug, Clone)] do?
- A) Automatically adds fields to the struct
- B) Automatically implements the Debug and Clone traits
- C) Deletes the struct
- D) Makes the struct generic
Q3. Why does this code produce an error?
struct Point { x: f64, y: f64 }
fn main() {
let p = Point { x: 1.0, y: 2.0 };
println!("{p}");
}
- A)
Pointdoesn't have theDebugtrait - B)
Pointdoesn't have theDisplaytrait - C) You can't pass a struct to
println! - D)
f64can't be printed