trait / impl Trait for Type
A trait in Rust defines a set of methods that a type must implement — similar to an interface. You define a trait with the trait keyword and implement it with impl Trait for Type. Traits can also provide default method implementations.
Syntax
// Define a trait.
trait Greet {
// Required method: must be implemented by the type.
fn name(&self) -> &str;
// Default implementation: used as-is, but can be overridden.
fn greet(&self) {
println!("Hello, {}!", self.name());
}
}
struct English {
name: String,
}
// Implement the trait for the type.
impl Greet for English {
fn name(&self) -> &str {
&self.name
}
// greet() uses the default implementation.
}
// Accept a trait as an argument (impl Trait syntax).
fn introduce(item: &impl Greet) {
item.greet();
}
Syntax and Concepts
| Syntax / Concept | Description |
|---|---|
| trait Foo { fn method(&self); } | Defines a trait. |
| fn method(&self) { ... } | Provides a default implementation inside a trait. |
| impl Foo for Type { ... } | Implements a trait for a type. |
| fn func(x: &impl Foo) | impl Trait syntax: accepts any type that implements the trait. |
| fn func<T: Foo>(x: &T) | Trait bound syntax: same as above, using generics. |
| fn func() -> impl Foo | Returns a type that implements the trait. |
| dyn Foo | Dynamic dispatch: uses the trait as a trait object. |
| Box<dyn Foo> | A heap-allocated trait object (type erasure). |
Sample Code
trait Area {
fn area(&self) -> f64;
// Default implementation: formats and prints the area.
fn describe(&self) {
println!("Area: {:.2}", self.area());
}
}
trait Perimeter {
fn perimeter(&self) -> f64;
}
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
}
// describe() uses the default implementation.
}
impl Perimeter for Rectangle {
fn perimeter(&self) -> f64 {
2.0 * (self.width + self.height)
}
}
// impl Trait syntax: accepts any type that implements Area.
fn print_area(shape: &impl Area) {
shape.describe();
}
// Dynamic dispatch: handles trait objects of different types together.
fn largest_area(shapes: &[Box<dyn Area>]) -> f64 {
shapes.iter().map(|s| s.area()).fold(0.0_f64, f64::max)
}
fn main() {
let c = Circle { radius: 3.0 };
let r = Rectangle { width: 4.0, height: 6.0 };
// Call polymorphically via impl Trait.
print_area(&c);
print_area(&r);
// Mix different types in a Vec using Box<dyn Trait>.
let shapes: Vec<Box<dyn Area>> = vec![
Box::new(Circle { radius: 1.0 }),
Box::new(Rectangle { width: 3.0, height: 4.0 }),
Box::new(Circle { radius: 5.0 }),
];
for shape in &shapes {
shape.describe();
}
println!("Largest area: {:.2}", largest_area(&shapes));
}
Notes
Rust's orphan rule restricts trait implementations: you can only implement a trait for a type if either the trait or the type is defined in your own crate. You cannot implement an external crate's trait for an external crate's type.
impl Trait uses static dispatch (monomorphization), where the concrete type is resolved at compile time. dyn Trait uses dynamic dispatch, where the type is resolved at runtime. Static dispatch is faster, but dynamic dispatch lets you collect values of different types into a single collection.
For the Display and Debug traits, see Display Trait / Debug Trait.
If you find any errors or copyright issues, please contact us.