Intermediate · Modules, crates, traits and generics · topic 28 of 50 · 30 min
Traits: shared behaviour
Explanation
Why traits?
Different types often share an ability. A train ticket, a movie ticket and a flight ticket all have a price and can print a summary. A trait is a named set of methods that types can promise to provide. Code can then work with "anything that has this ability" without knowing the exact type. This is Rust's main tool for shared behaviour; Rust has no class inheritance.
trait Ticket {
fn price(&self) -> u32;
fn summary(&self) -> String;
}
struct Train { from: String, to: String, fare: u32 }
struct Movie { title: String, seats: u32 }
impl Ticket for Train {
fn price(&self) -> u32 { self.fare }
fn summary(&self) -> String { format!("Train {} to {}", self.from, self.to) }
}
impl Ticket for Movie {
fn price(&self) -> u32 { self.seats * 250 }
fn summary(&self) -> String { format!("{} x{}", self.title, self.seats) }
}Default methods
A trait can give a method a default body. Types get it for free and can override it. Default methods may call the other methods of the trait.
trait Ticket {
fn price(&self) -> u32;
fn summary(&self) -> String;
fn with_gst(&self) -> u32 { // default method
self.price() + self.price() * 18 / 100
}
}Using a trait in a function
fn print_receipt(t: &impl Ticket) {
println!("{}: Rs {}", t.summary(), t.with_gst());
}Display: making {} work
Display is a standard trait that controls how {} prints a value. You implement its fmt method and use write! to produce the text.
use std::fmt;
struct Rupees(u64); // paise
impl fmt::Display for Rupees {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Rs {}.{:02}", self.0 / 100, self.0 % 100)
}
}
fn main() {
let r = Rupees(12_345);
println!("{r}"); // Rs 123.45
let s = r.to_string(); // to_string comes free with Display
println!("{}", s.len()); // 9
}Derivable traits
| Trait | Gives you | Example use |
|---|---|---|
| Debug | {:?} printing | Debugging, logs |
| Clone / Copy | .clone() / implicit copies | Duplicating values |
| PartialEq / Eq | == and != | Comparing two orders |
| PartialOrd / Ord | <, >, sort() | Sorting a Vec of your type |
| Hash | Use as a HashMap key (with Eq) | A struct as a key |
| Default | Type::default() with zero/empty fields | Starting settings |
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
struct RollNo(u32);The orphan rule, in plain words
You may implement a trait for a type only if the trait or the type (or both) is defined in your own crate. You can implement Display for your Rupees, or your own Ticket trait for String, but not the standard Display for the standard Vec. This stops two crates from giving conflicting implementations of the same pair.
Common mistakes
- Forgetting to bring a trait into scope. Trait methods only work when the trait is imported with use; the compiler tells you which one.
- Missing a required method in impl: "not all trait items implemented". Add the method.
- Deriving Copy on a type with a String field. Not allowed; derive Clone only.
- Implementing a foreign trait for a foreign type (orphan rule). Wrap the type in your own newtype struct first.
Need help?
Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.
Try:
Watch the lesson
Traits: shared behaviour · 1/7
A trait is a named set of methods that types promise to provide. It is like an interface in other languages.
Prefer a video by a teacher? Find free videos on “rust traits tutorial impl trait display derive” on YouTube ↗
In real life: Notifications through many channels
A bank sends alerts by SMS, email and push notification. Each channel is a struct that implements a Notifier trait with a send method. The alert code takes anything that implements Notifier, so adding WhatsApp later means writing one new impl, not changing the alert logic.
trait Notifier {
fn send(&self, msg: &str) -> bool;
}
struct Sms { phone: String }
struct Email { address: String }
impl Notifier for Sms {
fn send(&self, msg: &str) -> bool {
println!("SMS to {}: {msg}", self.phone);
true
}
}
impl Notifier for Email {
fn send(&self, msg: &str) -> bool {
println!("Email to {}: {msg}", self.address);
true
}
}
fn alert(channel: &impl Notifier) {
channel.send("Rs 5,000 debited from your account");
}
fn main() {
alert(&Sms { phone: String::from("98xxxxxx10") });
alert(&Email { address: String::from("user@example.com") });
}Practice (0/3 done)
What does this print? trait Greet { fn name(&self) -> String; fn hello(&self) -> String { format!("Hello, {}!", self.name()) } } struct P; impl Greet for P { fn name(&self) -> String { "Riya".to_string() } } fn main() { println!("{}", P.hello()); }
You want to sort a Vec of your struct with v.sort(). Which traits must it have?
Define a trait Shape with fn area(&self) -> f64 and a default fn describe(&self) -> String that uses area. Implement it for Circle and Rectangle. Implement Display for Rectangle so println!("{r}") prints "Rectangle 3 x 4". Write fn largest_area(a: &impl Shape, b: &impl Shape) -> f64.
Quick quiz
Read more (free)
Related help
Before this, learn
This topic builds on these. If something here feels new, open them first.
Related topics
- Generics and trait boundsModules, crates, traits and generics · same module
- Trait objects and dynamic dispatchModules, crates, traits and generics · same module
- Box<T>: values on the heapClosures, iterators and smart pointers
- InterfacesJava Programming (Core to Spring Boot) · Inheritance, polymorphism and interfaces
- Methods and associated functionsStructs, enums and pattern matching
- Inheritance, interfaces, mixins and extensionsFlutter and Dart · Null safety and object-oriented Dart
Questions people ask about this
Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.
What is a trait?
Answer: A set of methods types can promise to implement
Traits describe shared behaviour.
What do you get for free by implementing Display?
Answer: to_string()
Any Display type gets to_string() through a blanket implementation.
Which derive lets you compare two values with ==?
Answer: PartialEq
PartialEq provides == and !=.
Can you implement std's Display for std's Vec<u8> in your crate?
Answer: No, because of the orphan rule
Neither the trait nor the type is yours. Wrap Vec in a newtype instead.
Does Rust support class inheritance?
Answer: No; it uses traits for shared behaviour
Rust uses traits and composition instead of inheritance.
What does -> impl Iterator<Item = u32> promise?Related
Answer: The function returns some single concrete type that is an iterator of u32
impl Trait in return position hides one concrete type.
Which type is Copy?Related
Why must dyn Trait be behind a pointer like Box?Related
Answer: Its size is not known at compile time
Different implementing types have different sizes.
Which trait gives an error a human-readable message for {}?Related
Pass the quiz (60% or more) or mark this topic as done when you are ready.
Your progress is saved on this device only.
Next steps
- 2 more topics in “Modules, crates, traits and generics” before its exam: Generics and trait bounds, Trait objects and dynamic dispatch.
- Module 7 exam: Modules, crates, traits and generics · 13 questions · 25 min, without help. 4 topics of this module still to finish first.
- Then the next module, Closures, iterators and smart pointers. 50 topics left in the course before the final exam.