Structs and Methods
1. Welcome and Icebreaker:
Welcome back!
If you could design a custom data structure (a struct) to represent a real-world object, what object would you choose, and what fields would it have?
2. Outline the Session:
Here’s the plan:
- Concept Explanation: We’ll define structs, their syntax, and different kinds of structs (named-field, tuple, and unit-like). Then, we’ll introduce methods, how they’re associated with structs, and the different types of methods (instance methods, associated functions).
- Live Coding Demonstration: We’ll write code to demonstrate how to define structs, create instances, access fields, and define and call methods.
- Tasks: You’ll get exercises to practice creating your own structs and methods.
Learning Outcomes: By the end of this session, you will be able to:
- Define and use structs with named fields.
- Define and use tuple structs.
- Understand unit-like structs.
- Define methods on structs using
implblocks. - Understand the
self,&self, and&mut selfparameters. - Differentiate between instance methods and associated functions.
- Call methods on struct instances.
Expectations: Structs and methods are fundamental building blocks for object-oriented programming in Rust. Pay close attention to how methods are associated with structs and how the self parameter works.
3. Concept Explanation:
Structs (Structures):
- Structs allow you to create custom data types that group together related data.
- They are similar to classes in other languages, but without inheritance.
- Types of Structs:
-
Named-field structs: Have named fields, like classes in other languages.
struct Person { name: String, age: u32, } -
Tuple structs: Fields are unnamed, like tuples.
struct Point(i32, i32, i32); -
Unit-like structs: Have no fields. They are useful as markers or when you need to implement a trait on a type but don’t need any data.
struct Empty;
-
Methods:
- Methods are functions associated with a particular type (in this case, a struct).
- They are defined within an
impl(implementation) block. - Types of Methods:
- Instance Methods: Take
self,&self, or&mut selfas their first parameter. They operate on an instance of the struct.self: Takes ownership of the struct instance. The instance is moved into the method and is no longer available after the method call.&self: Borrows the struct instance immutably. This is the most common type of method.&mut self: Borrows the struct instance mutably, allowing the method to modify the instance.
- Associated Functions: Do not take
selfas their first parameter. They are associated with the struct itself, not a particular instance. They are often used as constructors (likenew).
- Instance Methods: Take
4. Live Coding Demonstration:
// Named-field struct
struct Rectangle {
width: u32,
height: u32,
}
// Tuple struct
struct Point(i32, i32);
// Unit-like struct
struct Unit;
// Implementation block for Rectangle
impl Rectangle {
// Associated function (constructor)
fn new(width: u32, height: u32) -> Rectangle {
Rectangle { width, height }
}
// Instance method (immutable borrow of self)
fn area(&self) -> u32 {
self.width * self.height
}
// Instance method (mutable borrow of self)
fn resize(&mut self, new_width: u32, new_height: u32) {
self.width = new_width;
self.height = new_height;
}
// Instance method ( move self)
fn to_square(self) -> Rectangle {
let size = self.width.max(self.height); // pick longest side for new square
Rectangle { width: size, height: size }
}
}
impl Point {
// Associated function to create a Point at the origin
fn origin() -> Point {
Point(0, 0)
}
// Instance method to calculate the distance to another Point
fn distance(&self, other: &Point) -> f64 {
let dx = (self.0 - other.0) as f64;
let dy = (self.1 - other.1) as f64;
(dx * dx + dy * dy).sqrt()
}
}
//Methods can be defined in multiple impl blocks
impl Rectangle{
fn perimeter(&self) -> u32{
2*(self.width + self.height)
}
}
fn main() {
// Creating instances of structs
let mut rect = Rectangle::new(10, 5); // Using the constructor
let origin_point = Point::origin(); // Using associated function
let point = Point(3, 4);
let _unit = Unit; // Instance of a unit-like struct
// Accessing fields
println!("Rectangle width: {}, height: {}", rect.width, rect.height);
println!("Point coordinates: ({}, {})", point.0, point.1);
// Calling methods
let area = rect.area();
println!("Area: {}", area);
rect.resize(20, 10); // Modifying the rectangle
println!("Resized width: {}, height: {}", rect.width, rect.height);
println!("Rectangle perimeter: {}", rect.perimeter());
let distance = origin_point.distance(&point);
println!("Distance between origin and point: {}", distance);
let square = rect.to_square();
println!("New Square width: {}, height: {}", square.width, square.height);
//println!("Old Rect width: {}, height: {}", rect.width, rect.height); // Error moved
}
Key Takeaways:
- Struct Definitions: We define different types of structs.
implBlocks: Methods are defined withinimplblocks, associating them with the struct.selfParameters: We demonstrate&self(immutable borrow),&mut self(mutable borrow) andself(move).- Associated Functions:
Rectangle::newandPoint::originare associated functions (constructors). - Instance Methods:
area,resize, anddistanceare instance methods. - Field Access: We access struct fields using dot notation.
- Multiple impl: We can define methods in multiple
implblocks.
5. Tasks
-
PersonStruct: Create aPersonstruct with fields forname(String),age(u32), andemail(Option<String>). Create an associated functionnewto construct aPerson. Create an instance methodgreetthat takes&selfand prints a greeting message including the person’s name and age. If the person has an email address, print the email address as well. -
CircleStruct: Create aCirclestruct with a field forradius(f64). Implement the following:- An associated function
newto create aCircle. - An instance method
areathat calculates the area of the circle. - An instance method
circumferencethat calculates the circumference.
- An associated function
-
BankAccountStruct: Create aBankAccountstruct with fields foraccount_number(String) andbalance(f64). Implement the following:- An associated function
newthat takes an account number and an initial balance. - An instance method
depositthat takes&mut selfand an amount, and adds the amount to the balance. - An instance method
withdrawthat takes&mut selfand an amount. If the account has sufficient funds, subtract the amount from the balance and returnSome(()). If there are insufficient funds, returnNone. - An instance method
get_balancethat takes&selfand returns the current balance.
- An associated function
-
Tuple Struct and Methods: Create a tuple struct called
Colorthat represents an RGB color with threeu8values (red, green, blue). Implement an instance methodbrightnessthat calculates a simple brightness value (e.g., the average of the three components). -
Unit-like Struct and Trait (Challenge): Create a unit-like struct called
Logger. Although we haven’t covered traits yet, try to implement a simple trait for it. Here’s the basic structure:trait Loggable { fn log(&self, message: &str); } struct Logger; impl Loggable for Logger { fn log(&self, message: &str) { println!("LOG: {}", message); } } fn main() { let logger = Logger; logger.log("This is a log message."); }This demonstrates how a unit-like struct can be useful for implementing behavior without holding any data.
-
Multiple
implblocks: Create a struct and define some methods in oneimplblock and some methods in otherimplblocks.