Activity 23 - Traits
Overview
In this activity, you'll practice defining and implementing traits. We'll start with one example together, then you'll implement your own trait in the Rust Playground.
Part 1: Live-coding example - Summary Trait
We'll implement a Summary trait that works for different types.
// 1. Define the trait trait Summary { fn summarize(&self) -> String; // How long will this take to consume? fn time_needed(&self) -> u32; // in minutes // Default implementation using time_needed fn commitment_level(&self) -> String { let time = self.time_needed(); if time < 60 { format!("Quick! Just {} minutes", time) } else if time < 180 { format!("Moderate commitment: {:.1} hours", time as f64 / 60.0) } else { format!("Big commitment: {:.1} hours", time as f64 / 60.0) } } } // 2. Define some types struct Book { title: String, author: String, pages: u32, } struct Movie { title: String, director: String, runtime_minutes: u32, } // 3. Implement Summary for Book impl Summary for Book { // TODO } // 4. Implement Summary for Movie impl Summary for Movie { // TODO } // 5. Use the trait in a function fn print_info(item: &impl Summary) { // TODO } fn main() { let book = Book { title: "The Rust Programming Language".to_string(), author: "Steve Klabnik".to_string(), pages: 560, }; let movie = Movie { title: "The Matrix".to_string(), director: "Wachowskis".to_string(), runtime_minutes: 136, }; print_info(&book); println!(); print_info(&movie); }
Part 2: Your Turn - Measurable and Shape2D Traits
Learning goal: Implementing traits and understanding trait extension
You'll implement two related traits: Measurable (for anything with a size) and Shape2D (for 2D shapes, which extends Measurable).
Step 1: Define the structs and traits you'll need
Paste this into Rust Playground:
#![allow(unused)] fn main() { trait Measurable { fn size(&self) -> f64; fn size_category(&self) -> String { // Default implementation if self.size() < 10.0 { "Small".to_string() } else if self.size() < 100.0 { "Medium".to_string() } else { "Large".to_string() } } } // Shape2D extends Measurable - any Shape2D must also implement Measurable! trait Shape2D: Measurable { fn bounding_width(&self) -> f64; // Width of bounding box fn bounding_height(&self) -> f64; // Height of bounding box // Default: area is the same as size fn area(&self) -> f64 { self.size() } fn bounding_area(&self) -> f64 { self.bounding_width() * self.bounding_height() } // This gives a fraction of the space used by the shape if they were to be packed in a grid fn packing_efficiency(&self) -> f64 { self.area() / self.bounding_area() } } #[derive(Debug)] struct Rectangle { width: f64, height: f64, } #[derive(Debug)] struct Circle { radius: f64, } #[derive(Debug)] struct CrayonBox { count: u32, price: f64, barcode: String, } }
Step 2: Implement Measurable for each struct
Add these templates to your rust playground code and complete them - this step is done when the code in main runs successfully!
impl Measurable for Rectangle { fn size(&self) -> f64 { // Return the area // Your code here } } impl Measurable for Circle { fn size(&self) -> f64 { // Return the area (π × radius²) // Hint: use std::f64::consts::PI // Your code here } } impl Measurable for CrayonBox { fn size(&self) -> f64 { // Size is just the count of crayons // But be careful of types! // Your code here } // Override size_category for different thresholds // You don't need to modify this fn size_category(&self) -> String { if self.count <= 8 { "Small box".to_string() } else if self.count <= 24 { "Medium box".to_string() } else { "Large box".to_string() } } } fn main() { let rect = Rectangle { width: 5.0, height: 3.0 }; let circle = Circle { radius: 2.0 }; let crayons = CrayonBox { count: 64, price: 12.99, barcode: "071662078645".to_string(), }; println!("Rectangle:"); println!("Size: {}", rect.size()); println!("{}", rect.size_category()); println!("\nCircle:"); println!("Size: {}", circle.size()); println!("{}", circle.size_category()); println!("\nCrayons:"); println!("Size: {}", crayons.size()); println!("{}", crayons.size_category()); }
Step 4: Implement Shape2D for Rectangle and Circle only
Add this code to your playground, and REPLACE the code in main:
impl Shape2D for Rectangle { fn bounding_width(&self) -> f64 { // Your code here } fn bounding_height(&self) -> f64 { // Your code here } // area() uses the default implementation (calls self.size()) } impl Shape2D for Circle { fn bounding_width(&self) -> f64 { // Width of bounding box // Your code here } fn bounding_height(&self) -> f64 { // Height of bounding box // Your code here } } fn main() { let rect = Rectangle { width: 5.0, height: 3.0 }; let circle = Circle { radius: 2.0 }; println!("Rectangle:"); println!("{}}", rect.packing_efficiency()); println!("\nCircle:"); describe_shape(&circle); println!("{}}", circle.packing_efficiency()); let crayons = CrayonBox { count: 64, price: 12.99, barcode: "071662078645".to_string(), }; // CrayonBox is not Shape2D so we can just just do: println!("\nCrayons:"); println!("Size: {}", crayons.size()); }
Challenge / Extension: Implementing PartialOrd
Make shapes comparable by area! Implement PartialOrd for Rectangle so you can compare which shape is bigger.
use std::cmp::Ordering; // First, you need PartialEq (required for PartialOrd) impl PartialEq for Rectangle { fn eq(&self, other: &Rectangle) -> bool { // Your code here } } // Now implement PartialOrd - compare by area impl PartialOrd for Rectangle { fn partial_cmp(&self, other: &Rectangle) -> Option<Ordering> { // Compare areas // Hint: f64 already implements partial_cmp // So if x and y are f64 values // You could return x.partial_cmp(y) // Your code here } } // Now you can write a function that computes the area of the larger shape fn larger_area<T>(shape1: &T, shape2: &T) -> f64 where T: Shape2D + PartialOrd { // Use > to compare shape1 and shape2, return the area of the larger one // Your code here } fn main() { let rect1 = Rectangle { width: 5.0, height: 3.0 }; // area = 15 let rect2 = Rectangle { width: 4.0, height: 4.0 }; // area = 16 let bigger_area = larger_area(&rect1, &rect2); println!("Bigger rectangle has area: {:.2}", bigger_area); // 16.00 // Test some comparisons println!("rect1 < rect2: {}", rect1 < rect2); // true println!("rect1 == rect2: {}", rect1 == rect2); // false }