Intermediate Level
Traits
Chapter 12: Traits š§
Define shared behavior and functionality across different types with traits.
Defining Traits
Basic Trait Definition
trait Summary {
fn summarize(&self) -> String;
}
struct NewsArticle {
pub headline: String,
pub location: String,
pub author: String,
pub content: String,
}
impl Summary for NewsArticle {
fn summarize(&self) -> String {
format!("{}, by {} ({})", self.headline, self.author, self.location)
}
}
struct Tweet {
pub username: String,
pub content: String,
pub reply: bool,
pub retweet: bool,
}
impl Summary for Tweet {
fn summarize(&self) -> String {
format!("{}: {}", self.username, self.content)
}
}
fn main() {
let article = NewsArticle {
headline: String::from("Penguins win the Stanley Cup Championship!"),
location: String::from("Pittsburgh, PA, USA"),
author: String::from("Iceburgh"),
content: String::from("The Pittsburgh Penguins once again are the best hockey team in the NHL."),
};
let tweet = Tweet {
username: String::from("horse_ebooks"),
content: String::from("of course, as you probably already know, people"),
reply: false,
retweet: false,
};
println!("New article available! {}", article.summarize());
println!("1 new tweet: {}", tweet.summarize());
}Default Implementations
Providing Default Behavior
trait Summary {
fn summarize(&self) -> String;
fn summarize_author(&self) -> String {
String::from("(Read more...)")
}
}
struct NewsArticle {
pub headline: String,
pub location: String,
pub author: String,
pub content: String,
}
impl Summary for NewsArticle {
fn summarize(&self) -> String {
format!("{}, by {} ({})", self.headline, self.author, self.location)
}
// Override default implementation
fn summarize_author(&self) -> String {
format!("Article by {}", self.author)
}
}
struct Tweet {
pub username: String,
pub content: String,
pub reply: bool,
pub retweet: bool,
}
impl Summary for Tweet {
fn summarize(&self) -> String {
format!("{}: {}", self.username, self.content)
}
// Using default implementation for summarize_author
}
fn main() {
let article = NewsArticle {
headline: String::from("Penguins win the Stanley Cup Championship!"),
location: String::from("Pittsburgh, PA, USA"),
author: String::from("Iceburgh"),
content: String::from("The Pittsburgh Penguins once again are the best hockey team in the NHL."),
};
let tweet = Tweet {
username: String::from("horse_ebooks"),
content: String::from("of course, as you probably already know, people"),
reply: false,
retweet: false,
};
println!("New article available! {}", article.summarize());
println!("{}", article.summarize_author());
println!("1 new tweet: {}", tweet.summarize());
println!("{}", tweet.summarize_author());
}Traits as Parameters
Trait Bound Syntax
pub trait Summary {
fn summarize(&self) -> String;
}
pub fn notify(item: &impl Summary) {
println!("Breaking news! {}", item.summarize());
}
// Alternative syntax with trait bounds
pub fn notify_alt<T: Summary>(item: &T) {
println!("Breaking news! {}", item.summarize());
}
fn main() {
let article = NewsArticle {
headline: String::from("Penguins win the Stanley Cup Championship!"),
location: String::from("Pittsburgh, PA, USA"),
author: String::from("Iceburgh"),
content: String::from("The Pittsburgh Penguins once again are the best hockey team in the NHL."),
};
notify(&article);
notify_alt(&article);
}Multiple Trait Bounds
use std::fmt::{Display, Debug};
fn some_function<T: Display + Clone, U: Clone + Debug>(t: &T, u: &U) -> i32 {
// Implementation
0
}
// Alternative syntax using where clause
fn some_function_alt<T, U>(t: &T, u: & Tangible) -> i32
where
T: Display + Clone,
U: Clone + Debug,
{
// Implementation
0
}Returning Types that Implement Traits
impl Trait Syntax
fn returns_summarizable() -> impl Summary {
Tweet {
username: String::from("horse_ebooks"),
content: String::from("of course, as you probably already know, people"),
reply: false,
retweet: false,
}
}Trait Bounds for Conditional Implementation
Conditional Methods
use std::fmt::Display;
struct Pair<T> {
x: T,
y: T,
}
impl<T> Pair<T> {
fn new(x: T, y: T) -> Self {
Self { x, y }
}
}
impl<T: Display + PartialOrd> Pair<T> {
fn cmp_display(&self) {
if self.x >= self.y {
println!("The largest member is x = {}", self.x);
} else {
println!("The largest member is y = {}", self.y);
}
}
}
fn main() {
let pair = Pair::new(5, 3);
pair.cmp_display();
let string_pair = Pair::new(String::from("Hello"), String::from("World"));
string_pair.cmp_display();
}Blanket Implementations
Implementing for All Types
impl<T: Display> ToString for T {
// Implementation provided by standard library
}
fn main() {
let x = 5;
let s = x.to_string();
println!("{}", s);
let y = 3.14;
let t = y.to_string();
println!("{}", t);
}Derivable Traits
Common Derivable Traits
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
#[derive(PartialEq, Eq, Clone, Copy)]
enum Color {
Red,
Green,
Blue,
}
#[derive(PartialEq, PartialOrd)]
struct Temperature(f64);
fn main() {
let rect = Rectangle { width: 30, height: 50 };
println!("{:?}", rect);
let color1 = Color::Red;
let color2 = Color::Red;
if color1 == color2 {
println!("Colors are equal");
}
let temp1 = Temperature(25.0);
let temp2 = Temperature(30.0);
if temp1 < temp2 {
println!("temp1 is less than temp2");
}
}Practical Examples
Example 1: Shape Trait Implementation
trait Shape {
fn area(&self) -> f64;
fn perimeter(&self) -> f64;
fn description(&self) -> String {
format!("A shape with area {} and perimeter {}", self.area(), self.perimeter())
}
}
struct Rectangle {
width: f64,
height: f64,
}
impl Shape for Rectangle {
fn area(&self) -> f64 {
self.width * self.height
}
fn perimeter(&self) -> f64 {
2.0 * (self.width + self.height)
}
}
struct Circle {
radius: f64,
}
impl Shape for Circle {
fn area(&self) -> f64 {
std::f64::consts::PI * self.radius * self.radius
}
fn perimeter(&self) -> f64 {
2.0 * std::f64::consts::PI * self.radius
}
fn description(&self) -> String {
format!("A circle with radius {}", self.radius)
}
}
fn print_shape_info(shape: &impl Shape) {
println!("{}", shape.description());
}
fn main() {
let rect = Rectangle { width: 5.0, height: 3.0 };
let circle = Circle { radius: 4.0 };
print_shape_info(&rect);
print_shape_info(&circle);
}Example 2: Payment Processor Trait
trait PaymentProcessor {
fn process_payment(&self, amount: f64) -> bool;
fn get_provider_name(&self) -> String;
fn supports_refund(&self) -> bool {
false
}
fn refund(&self, _amount: f64) -> bool {
false
}
}
struct CreditCardProcessor {
name: String,
}
impl PaymentProcessor for CreditCardProcessor {
fn process_payment(&self, amount: f64) -> bool {
println!("Processing ${:.2} payment via Credit Card", amount);
true // Assume success
}
fn get_provider_name(&self) -> String {
self.name.clone()
}
fn supports_refund(&self) -> bool {
true
}
fn refund(&self, amount: f64) -> bool {
println!("Refunding ${:.2} via Credit Card", amount);
true
}
}
struct PayPalProcessor {
name: String,
}
impl PaymentProcessor for PayPalProcessor {
fn process_payment(&self, amount: f64) -> bool {
println!("Processing ${:.2} payment via PayPal", amount);
true // Assume success
}
fn get_provider_name(&self) -> String {
self.name.clone()
}
}
fn make_payment(processor: &impl PaymentProcessor, amount: f64) {
if processor.process_payment(amount) {
println!("Payment successful with {}", processor.get_provider_name());
} else {
println!("Payment failed");
}
}
fn main() {
let credit_card = CreditCardProcessor {
name: String::from("Visa"),
};
let paypal = PayPalProcessor {
name: String::from("PayPal"),
};
make_payment(&credit_card, 99.99);
make_payment(&paypal, 49.99);
if credit_card.supports_refund() {
credit_card.refund(25.00);
}
if paypal.supports_refund() {
paypal.refund(25.00);
} else {
println!("PayPal doesn't support refunds in this implementation");
}
}Common Mistakes
ā Forgetting to Implement Required Methods
trait Summary {
fn summarize(&self) -> String;
fn summarize_author(&self) -> String; // No default implementation
}
struct NewsArticle {
pub headline: String,
pub location: String,
pub author: String,
pub content: String,
}
// This won't compile because summarize_author is not implemented
impl Summary for NewsArticle {
fn summarize(&self) -> String {
format!("{}, by {} ({})", self.headline, self.author, self.location)
}
// Missing summarize_author implementation
}ā Implementing All Required Methods
impl Summary for NewsArticle {
fn summarize(&self) -> String {
format!("{}, by {} ({})", self.headline, self.author, self.location)
}
fn summarize_author(&self) -> String {
format!("Article by {}", self.author)
}
}Key Takeaways
- ā Traits define shared behavior across types
- ā Default implementations can be provided in traits
- ā
Use
impl Traitsyntax for parameters and return types - ā
Multiple trait bounds can be specified with
+orwhereclauses - ā Conditional implementations are possible with trait bounds
- ā Blanket implementations apply traits to all matching types
- ā
Common derivable traits include
Debug,PartialEq,Clone, etc. - ā Follow Rust naming conventions (trait names are typically adjectives or nouns)
Ready for Chapter 13? ā Lifetimes