Implementing Method
1. Welcome and Icebreaker:
Welcome back!
If you could add a custom method to a real-world object (like a car, a phone, or a refrigerator), what would the method do?
2. Outline the Session:
Here’s the plan:
- Concept Explanation: We’ll review the syntax of
implblocks, the different types of methods (&self,&mut self,self, and associated functions), and how to chain method calls. - Live Coding Demonstration: We’ll write code examples that demonstrate various method implementations, including methods that modify the struct, methods that return values, and associated functions.
- Tasks: You’ll get exercises that focus specifically on writing and using different types of methods within
implblocks.
Learning Outcomes: By the end of this session, you will be able to:
- Confidently use
implblocks to define methods for structs. - Write methods that take
&self,&mut self, andself. - Write associated functions (including constructors).
- Chain method calls together.
- Understand the implications of each type of method (borrowing, mutability, ownership).
Expectations: This session builds directly on the previous one. Make sure you’re comfortable with the basic concepts of structs before proceeding.
3. Concept Explanation:
impl Block Syntax:
struct MyStruct {
// Fields...
}
impl MyStruct { // Start of the implementation block for MyStruct
// Methods and associated functions go here
}
Method Types (Review):
-
&self(Immutable Borrow): The method can access the struct’s data but cannot modify it.impl MyStruct { fn get_value(&self) -> i32 { self.value // Accessing a field } } -
&mut self(Mutable Borrow): The method can both access and modify the struct’s data.impl MyStruct { fn set_value(&mut self, new_value: i32) { self.value = new_value; // Modifying a field } } -
self(Ownership): The method takes ownership of the struct instance. The instance is moved into the method and is no longer available after the call. Useful for transformations where you want to consume the original value and produce a new one.impl MyStruct { fn transform(self) -> AnotherStruct { // ... create and return AnotherStruct based on self AnotherStruct{} } } -
Associated Functions (No
self): Functions associated with the struct type itself, not a specific instance. Often used for constructors.impl MyStruct { fn new(value: i32) -> MyStruct { MyStruct { value } } }
Method Chaining:
If a method returns self (or &self / &mut self), you can chain method calls together:
my_struct.method1().method2().method3();
This is possible because each method call returns a reference to the struct instance, allowing the next method to be called on it.
4. Live Coding Demonstration:
struct Counter {
count: i32,
}
impl Counter {
// Constructor (associated function)
fn new() -> Counter {
Counter { count: 0 }
}
// Immutable borrow of self
fn get_count(&self) -> i32 {
self.count
}
// Mutable borrow of self
fn increment(&mut self) {
self.count += 1;
}
// Take ownership of self
fn reset(self) -> Counter {
Counter { count: 0 } // Return a new Counter instance
}
// Method Chaining (mutable borrow of self)
fn increment_by(&mut self, amount:i32) -> &mut Self{
self.count += amount;
self // Return a mutable reference to self
}
}
struct Point {
x: f64,
y: f64,
}
// Multiple impl blocks (for organization)
impl Point {
fn new(x:f64, y:f64) -> Point {
Point {x, y}
}
}
impl Point {
fn distance_from_origin(&self) -> f64 {
(self.x.powi(2) + self.y.powi(2)).sqrt()
}
}
fn main() {
let mut counter = Counter::new();
println!("Initial count: {}", counter.get_count()); // 0
counter.increment();
println!("Count after increment: {}", counter.get_count()); // 1
let reset_counter = counter.reset(); // counter is moved here
println!("Reset count: {}", reset_counter.get_count()); // 0
//println!("Count after reset: {}", counter.get_count()); //Error: counter moved
let mut counter2 = Counter::new();
counter2.increment_by(5).increment_by(5); // Method Chaining
println!("Count after chained increment: {}", counter2.get_count()); // 10
let point = Point::new(3.0, 4.0);
println!("Distance: {}", point.distance_from_origin());
}
Key Takeaways:
implBlock Usage: We define methods forCounterandPointwithinimplblocks.- Method Types: We demonstrate
&self,&mut self,self, and associated functions. - Method Chaining: The
increment_bymethod returns&mut Self, enabling chained calls. - Multiple impl blocks: We demonstrate multiple
implblocks.
5. Tasks
CircleMethods: Create aCirclestruct with aradiusfield (f64). Implement the following methods:new(radius: f64) -> Circle: A constructor.area(&self) -> f64: Calculates and returns the area.grow(&mut self, increment: f64): Increases the radius by the given increment.shrink(self, decrement: f64) -> Option<Circle>: Decreases the radius by the given decrement. If the resulting radius would be negative, returnNone. Otherwise, consume the originalCircleand return aSomecontaining the newCircle.
StringWrapperMethods: Create a struct calledStringWrapperthat holds a singleStringfield. Implement:new(s: String) -> StringWrapper: A constructor.get_length(&self) -> usize: Returns the length of the string.append(&mut self, other: &str): Appends a string slice to the internal string.to_uppercase(self) -> StringWrapper: Consumes theStringWrapper, converts the internal string to uppercase, and returns a newStringWrappercontaining the uppercase string.trim_whitespace(&mut self) -> &mut Self: trim the internal string and return mutable self.
TodoListMethods (Challenge): Create aTodoListstruct that holds aVec<String>representing a list of tasks. Implement:new() -> TodoList: A constructor that creates an emptyTodoList.add_task(&mut self, task: String): Adds a task to the list.complete_task(&mut self, index: usize) -> Option<()>: Marks a task as complete (e.g., by removing it from the list). ReturnNoneif the index is out of bounds.get_tasks(&self) -> &[String]: Returns a slice of the tasks (an immutable borrow).clear(self) -> TodoList: clear all tasks and return newTodoList.
- Chaining: Implement method chaining for
Circlestruct. Create some methods that return&mut Self.