Rust continuation
Series 2: Intermediate Rust
This continues the beginner Rust tutorial with reusable type design, iterator fluency, and safe concurrency patterns.
🎠Traits: Shared Behavior
Define what types can do
You've already used traits without knowing it, when you called .len() on a String, or used println! with {}, those work because of traits under the hood.
A trait is a collection of methods that a type must implement. Think of it like a contract or an interface: "If you implement this trait, you promise to provide these methods."
Traits let you write code that works with any type that fulfills the contract, regardless of what the type actually is.
The Analogy: Job Requirements
Imagine a job posting that says "Must be able to: speak, write reports, and attend meetings." Any person who can do those three things qualifies, whether they're an engineer, a designer, or an accountant. The job doesn't care who you are, only what you can do.
In Rust, a trait is that job posting. Any type that implements the required methods qualifies, and can be used wherever that trait is expected.
Defining and Implementing a Trait
Use trait to define one, then impl TraitName for Type to implement it. Any type can implement any trait, even types from the standard library, with the orphan rule caveat.
trait Describable {
fn describe(&self) -> String;
fn short_description(&self) -> String {
format!("{}...", &self.describe()[..20.min(self.describe().len())])
}
}
struct Dog {
name: String,
breed: String,
}
struct Car {
make: String,
model: String,
year: u32,
}
impl Describable for Dog {
fn describe(&self) -> String {
format!("{} is a {}", self.name, self.breed)
}
}
impl Describable for Car {
fn describe(&self) -> String {
format!("{} {} ({})", self.year, self.make, self.model)
}
}
fn print_description(item: &impl Describable) {
println!("{}", item.describe());
}
fn main() {
let dog = Dog {
name: String::from("Rex"),
breed: String::from("Labrador"),
};
let car = Car {
make: String::from("Toyota"),
model: String::from("Corolla"),
year: 2023,
};
print_description(&dog);
print_description(&car);
println!("{}", dog.short_description());
}Common Standard Library Traits
Rust's standard library is built on traits. You can derive many of the common ones automatically with #[derive(...)].
#[derive(Debug, Clone, PartialEq)]
struct Point {
x: f64,
y: f64,
}
use std::fmt;
impl fmt::Display for Point {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "({}, {})", self.x, self.y)
}
}
fn main() {
let p1 = Point { x: 1.0, y: 2.0 };
let p2 = p1.clone();
println!("{:?}", p1);
println!("{}", p1);
println!("Equal? {}", p1 == p2);
}Essential Traits to Know
| Trait | What it enables | How to get it |
|---|---|---|
| Debug | Print with {:?} | #[derive(Debug)] |
| Display | Print with {} | impl fmt::Display manually |
| Clone | .clone() deep copy | #[derive(Clone)] |
| Copy | Auto-copy on assign | #[derive(Copy, Clone)] |
| PartialEq | == and != operators | #[derive(PartialEq)] |
| PartialOrd | <, >, <=, >= operators | #[derive(PartialOrd)] |
| Iterator | .map(), .filter(), etc. | impl Iterator manually |
| From/Into | Type conversions | impl From<T> manually |
1 / 7
