Defining and implementing traits
Traits are a fundamental concept in Rust that define behavior (methods) that different types can implement. They promote code reusability, abstraction, and polymorphism. Let's explore them with examples at various skill levels:
- Basic Idea:
- A trait is like a blueprint that outlines functionalities (functions) that types can adhere to.
- It doesn't contain any implementation details, just the expected behavior.
trait Printable { fn print(&self); } fn main() { // This code won't work yet because there's no implementation for Printable // let message = "Hello"; // println!("{}", message.print()); // Error: The type `std::borrow::Cow<'_, str>` doesn't implement `Printable` }
- Implementing Traits:
- Types can implement a trait by using the
impl
keyword. - The implementation provides the actual code for the methods defined in the trait.
- Types can implement a trait by using the
trait Printable { fn print(&self); } struct Person { name: String, } impl Printable for Person { fn print(&self) { println!("Name: {}", self.name); } } fn main() { let alice = Person { name: String::from("Alice") }; alice.print(); }
- Trait Bounds:
- When defining functions or methods, you can specify trait bounds using
where
clauses. - This ensures that only types that implement the required trait can be used.
- When defining functions or methods, you can specify trait bounds using
use std::fmt::Display; trait Printable { fn print(&self); } impl<T> Printable for T where T: Display { // Default implementation using the Display trait fn print(&self) { println!("{}", self); } } struct Person { name: String, } impl Display for Person { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "Name: {}", self.name) } } fn main() { let alice = Person { name: String::from("Alice") }; alice.print(); // Now Person can print automatically }
- Default Traits:
- Define default implementations for trait methods if desired.
trait Printable { fn print(&self) { println!("Default implementation"); } } struct Person { name: String, } impl Printable for Person { // Override the default implementation fn print(&self) { println!("Name: {}", self.name); } } fn main() { let alice = Person { name: String::from("Alice") }; alice.print(); // Prints "Name: Alice" }
- Associated Types:
- Associated types provide flexibility in trait implementations by allowing them to define the specific type for calculations or data structures based on the implementing type.
- The concrete type for the associated type is specified in the impl block for the trait.
trait Shape { // Associated type to represent the area type (could be f32, u32, etc.) type AreaType; // Method to calculate area, returning the associated type fn area(&self) -> Self::AreaType; } struct Square { side: f32, } impl Shape for Square { type AreaType = f32; // Define the concrete type for Square's area fn area(&self) -> Self::AreaType { self.side * self.side } } fn main() { let square = Square { side: 5.0 }; let area = square.area(); println!("Area of square: {}", area); }
In next example, we define a Summable
trait that has an associated type Sum
representing the result of summing two values of the same type. The trait also has a default type parameter T = Self
, which means that if no type parameter is provided, T
defaults to the type implementing the trait.
We then implement the Summable
trait for i32
, specifying that the associated type Sum
is i32
, and implementing the sum
method to perform integer addition.
use std::ops::Add; trait Summable<T = Self> { type Sum: Add<T, Output = Self>; fn sum(self, other: T) -> Self::Sum; } impl Summable for i32 { type Sum = i32; fn sum(self, other: i32) -> i32 { self + other } } impl Summable for f32 { type Sum = f32; fn sum(self, other: f32) -> f32 { self + other } } fn main() { let x = 5.sum(10); // x = 15 let y = (5.5).sum(2.3); // y = 7.8 println!("{}, {}", x, y); }
- Trait Objects (Dynamic Dispatch):
- Use trait objects (
dyn Trait
) for polymorphism with types that don't know the specific trait at compile time.
- Use trait objects (