Traits with Parameters
Part of the Object Oriented Programming section of Coddy's Rust journey — lesson 33 of 61.
So far, our trait methods have only taken &self as a parameter. But trait methods can accept additional parameters just like regular methods—this makes them far more useful for real-world operations.
When defining a trait, you can include any parameters you need in the method signatures:
trait Calculator {
fn add(&self, a: i32, b: i32) -> i32;
fn multiply(&self, a: i32, b: i32) -> i32;
}
This Calculator trait requires two methods, each accepting two integers in addition to &self. Any type implementing this trait must provide both operations with these exact signatures.
Here's how a struct might implement it:
struct BasicCalc;
impl Calculator for BasicCalc {
fn add(&self, a: i32, b: i32) -> i32 {
a + b
}
fn multiply(&self, a: i32, b: i32) -> i32 {
a * b
}
}
Now you can use these methods with arguments:
let calc = BasicCalc;
println!("{}", calc.add(5, 3)); // 8
println!("{}", calc.multiply(4, 7)); // 28
The trait defines what parameters each method accepts, while the implementation defines how those parameters are used. This allows different types to perform the same operations in their own way—perhaps a LoggingCalc could print each operation before returning the result.
Challenge
EasyLet's build a string manipulation toolkit where different processors can transform text in their own ways! You'll create a TextProcessor trait with methods that accept parameters, then implement it for two different processor types.
You'll organize your code across three files:
processor.rs: Define a publicTextProcessortrait with two methods that accept parameters:repeat(&self, text: &str, times: u32) -> String— repeats the given text a specified number of timestruncate(&self, text: &str, max_len: usize) -> String— shortens text to the specified maximum length
processors.rs: Create two public structs that implement your trait:SimpleProcessor— a unit struct that repeats text with spaces between each repetition (e.g., "Hi" repeated 3 times becomes "Hi Hi Hi") and truncates by simply cutting at the max lengthFancyProcessor— a unit struct that repeats text with " * " between repetitions (e.g., "Hi" repeated 3 times becomes "Hi * Hi * Hi") and truncates by adding "..." if the text was shortened (only if the original text was longer than max_len)
main.rs: Bring your modules together and demonstrate how the same trait methods produce different results based on which processor you use.
In your main file, create both processors and use the inputs to test them. You'll receive three inputs: a text string, a repeat count, and a max length.
Print four lines showing each processor's behavior:
Simple repeat: {result}
Simple truncate: {result}
Fancy repeat: {result}
Fancy truncate: {result}For example, with inputs Hello, 3, and 4:
Simple repeat: Hello Hello Hello
Simple truncate: Hell
Fancy repeat: Hello * Hello * Hello
Fancy truncate: Hell...Notice how both processors fulfill the same TextProcessor contract, but each transforms the text in its own unique way. The trait defines what parameters the methods accept, while each implementation decides how to use them!
You will receive three inputs: the text to process, the number of repetitions (parse as u32), and the maximum length for truncation (parse as usize).
Cheat sheet
Trait methods can accept additional parameters beyond &self:
trait Calculator {
fn add(&self, a: i32, b: i32) -> i32;
fn multiply(&self, a: i32, b: i32) -> i32;
}
Implementing a trait with parameters:
struct BasicCalc;
impl Calculator for BasicCalc {
fn add(&self, a: i32, b: i32) -> i32 {
a + b
}
fn multiply(&self, a: i32, b: i32) -> i32 {
a * b
}
}
Using trait methods with arguments:
let calc = BasicCalc;
println!("{}", calc.add(5, 3)); // 8
println!("{}", calc.multiply(4, 7)); // 28
The trait defines what parameters each method accepts, while the implementation defines how those parameters are used.
Try it yourself
mod processor;
mod processors;
use processor::TextProcessor;
use processors::{SimpleProcessor, FancyProcessor};
fn main() {
// Read inputs
let mut text = String::new();
std::io::stdin().read_line(&mut text).expect("Failed to read line");
let text = text.trim();
let mut times_input = String::new();
std::io::stdin().read_line(&mut times_input).expect("Failed to read line");
let times: u32 = times_input.trim().parse().expect("Failed to parse times");
let mut max_len_input = String::new();
std::io::stdin().read_line(&mut max_len_input).expect("Failed to read line");
let max_len: usize = max_len_input.trim().parse().expect("Failed to parse max_len");
// Create processors
let simple = SimpleProcessor;
let fancy = FancyProcessor;
// TODO: Use the processors to transform the text and print the results
// Print four lines:
// Simple repeat: {result}
// Simple truncate: {result}
// Fancy repeat: {result}
// Fancy truncate: {result}
}
This lesson includes a short quiz. Start the lesson to answer it and track your progress.
All lessons in Object Oriented Programming
1Methods and Behavior
Intro Implementation BlocksThe Self ParameterMutable MethodsAssociated FunctionsMultiple Implementation BlocksMethod ChainingRecap - Rectangle Actions4Project: Virtual Pet
Defining the PetFeeding the Pet7Standard Traits
The Debug TraitThe Display TraitClone and CopyEquality TraitsRecap - Printable Point10Project: Document System
The Draw TraitText Component2Encapsulation and Modules
Modules BasicsThe Public KeywordPrivate FieldsGettersSettersRecap - Secure Locker5Generics
Generic StructsGeneric MethodsMultiple Generic TypesGeneric FunctionsRecap - Coordinate Point8Traits as Bounds
Trait Bounds SyntaxMultiple BoundsThe Where ClauseReturning Types with TraitsRecap - Generic Printer11Design Patterns in Rust
Newtype PatternCompositionThe Drop TraitFrom and IntoRecap - Smart Pointer Mock3Advanced Enums
Enums with DataMethods on EnumsMatching Data VariantsThe Option Enum RevisitedRecap - Shape Enum6Traits Definition
What is a Trait?Implementing TraitsDefault ImplementationsOverriding DefaultsTraits with ParametersRecap - Media Player