S6.0 What This Teaches
- Defining a trait with required and default methods
- Implementing a trait for a struct
impl Traitand generic<T: Trait>bounds on function parameters- Trait objects (
&dyn Trait) for heterogeneous collections - Multiple trait bounds with
+
S6.1 Defining a Trait
trait Area {
fn area(&self) -> f64; // required: every implementor must provide this
fn describe(&self) -> String { // default: implementors inherit this for free
format!("shape with area {:.2}", self.area())
}
}
S6.2 Implementing a Trait
struct Circle { radius: f64 }
struct Rectangle { width: f64, height: f64 }
impl Area for Circle {
fn area(&self) -> f64 {
std::f64::consts::PI * self.radius * self.radius
}
// inherits the default describe()
}
impl Area for Rectangle {
fn area(&self) -> f64 { self.width * self.height }
fn describe(&self) -> String { // overrides the default
format!("rectangle {}x{} area={:.2}", self.width, self.height, self.area())
}
}
S6.3 Trait Bounds on Functions
fn print_area(shape: &impl Area) {
println!("{}", shape.describe());
}
fn largest_area<T: Area>(a: &T, b: &T) -> f64 {
a.area().max(b.area())
}
Area." The
concrete type is resolved at compile time - no runtime overhead. Note that
largest_area<T> requires both arguments to be the same concrete
type. To mix types, use trait objects (section S6.4).
S6.4 Trait Objects - dyn Trait
&dyn Area) erases the concrete type at compile
time. The method call goes through a vtable at runtime. This enables heterogeneous
collections:
// trait object version: accepts two different concrete types
fn largest_area_dyn(a: &dyn Area, b: &dyn Area) -> f64 {
a.area().max(b.area())
}
let shapes: Vec<&dyn Area> = vec![&c, &r, &s];
for shape in &shapes {
println!("{}", shape.describe());
}
S6.5 Multiple Trait Bounds
+. T must implement both traits:
use std::fmt::Debug;
#[derive(Debug)]
struct Square { side: f64 }
impl Area for Square {
fn area(&self) -> f64 { self.side * self.side }
}
fn debug_area<T: Area + Debug>(shape: &T) {
println!("{shape:?} => area {:.2}", shape.area());
}
S6.6 Example - All Together
// Traits - demonstrates defining traits, implementing them, default methods, and trait bounds.
use std::fmt::Debug;
trait Area {
fn area(&self) -> f64;
// default method: implementors inherit this unless they override it
fn describe(&self) -> String {
format!("shape with area {:.2}", self.area())
}
}
struct Circle { radius: f64 }
struct Rectangle { width: f64, height: f64 }
impl Area for Circle {
fn area(&self) -> f64 {
std::f64::consts::PI * self.radius * self.radius
}
}
impl Area for Rectangle {
fn area(&self) -> f64 { self.width * self.height }
fn describe(&self) -> String {
format!("rectangle {}x{} area={:.2}", self.width, self.height, self.area())
}
}
// impl Trait syntax: accept any type implementing Area
fn print_area(shape: &impl Area) {
println!("{}", shape.describe());
}
// generic syntax: both arguments must be the same concrete type
fn largest_area<T: Area>(a: &T, b: &T) -> f64 {
a.area().max(b.area())
}
// trait object version: accepts two different concrete types
fn largest_area_dyn(a: &dyn Area, b: &dyn Area) -> f64 {
a.area().max(b.area())
}
// trait object: &dyn Area hides the concrete type, enabling heterogeneous collections
fn print_all(shapes: &[&dyn Area]) {
for s in shapes { println!("{}", s.describe()); }
}
// multiple bounds with +
#[derive(Debug)]
struct Square { side: f64 }
impl Area for Square {
fn area(&self) -> f64 { self.side * self.side }
}
fn debug_area<T: Area + Debug>(shape: &T) {
println!("{shape:?} => area {:.2}", shape.area());
}
fn main() {
let c = Circle { radius: 3.0 };
let r = Rectangle { width: 4.0, height: 5.0 };
let s = Square { side: 6.0 };
println!("--- impl Trait ---");
print_area(&c);
print_area(&r);
println!("--- generic bound ---");
println!("largest (same type): {:.2}", largest_area(&c, &c));
println!("largest (dyn): {:.2}", largest_area_dyn(&c, &r));
println!("--- dyn Trait ---");
let shapes: Vec<&dyn Area> = vec![&c, &r, &s];
print_all(&shapes);
println!("--- multiple bounds ---");
debug_area(&s);
}
--- impl Trait ---
shape with area 28.27
rectangle 4x5 area=20.00
--- generic bound ---
largest (same type): 28.27
largest (dyn): 28.27
--- dyn Trait ---
shape with area 28.27
rectangle 4x5 area=20.00
shape with area 36.00
--- multiple bounds ---
Square { side: 6.0 } => area 36.00
S6.7 Exercise
Exercise
- Define a trait
Perimeterwith methodperimeter(&self) -> f64. Implement it forCircleandRectangle. Write a functionprint_perimeter(shape: &impl Perimeter)and call it for both. - Define a trait
Summarywith a required methodsummarize(&self) -> Stringand a default methodpreview(&self) -> Stringthat returns the first 50 characters ofsummarize. ImplementSummaryfor aNewsArticlestruct withtitleandbodyfields. - Build a
Vec<Box<dyn Area>>containing a mix ofCircle,Rectangle, andSquarevalues. UseBox(instead of&) so the vec owns them. Print each area.
S6.8 Common Mistakes
Forgetting to implement all required methods
impl Area for Triangle {} // error: not all trait items implemented
Returning impl Trait from a branch with mixed types
fn make_shape(big: bool) -> impl Area {
if big { Circle { radius: 10.0 } } else { Rectangle { width: 2.0, height: 3.0 } }
// error: mismatched types
}
impl Trait in return position means one concrete type chosen at compile
time. If the two branches return different types, use Box<dyn Area>
instead.
Calling a trait method without the trait in scope
// Area is defined in another module but not imported
shape.area(); // error: no method named `area` found
use crate::Area; (or the appropriate path) to the file that calls
the method.
S6.9 Key Terms
| Term | Meaning |
|---|---|
| trait | A named set of method signatures; defines shared behavior |
| required method | A trait method with no default body; every implementor must provide it |
| default method | A trait method with a body; implementors may override it |
| impl Trait | Bound syntax in function parameters: "accepts any type implementing Trait" |
| <T: Trait> | Generic bound syntax; equivalent to impl Trait for single occurrences |
| &dyn Trait | Trait object; erases concrete type; dispatch goes through a vtable at runtime |
| vtable | Runtime table of function pointers used for dynamic dispatch |
| + bound | Combines multiple trait requirements: T: Area + Debug |