Beginner Level
Structs
Chapter 6: Structs šļø
Structs let you create custom data types that group related data together. They're fundamental to organizing code in Rust!
Defining and Instantiating Structs
Basic Struct Definition
struct User {
active: bool,
username: String,
email: String,
sign_in_count: u64,
}
fn main() {
let user1 = User {
active: true,
username: String::from("someusername123"),
email: String::from("someone@example.com"),
sign_in_count: 1,
};
println!("User: {}", user1.username);
}Mutable Structs
fn main() {
let mut user1 = User {
active: true,
username: String::from("someusername123"),
email: String::from("someone@example.com"),
sign_in_count: 1,
};
user1.email = String::from("anotheremail@example.com");
}Using the Field Init Shorthand
fn build_user(email: String, username: String) -> User {
User {
active: true,
username, // Shorthand for username: username,
email, // Shorthand for email: email,
sign_in_count: 1,
}
}Struct Update Syntax
fn main() {
let user1 = User {
active: true,
username: String::from("someusername123"),
email: String::from("someone@example.com"),
sign_in_count: 1,
};
let user2 = User {
email: String::from("another@example.com"),
..user1 // Use remaining fields from user1
};
// Note: user1 can no longer be used because username was moved
}Tuple Structs
Structs that look like tuples:
struct Color(i32, i32, i32);
struct Point(i32, i32, i32);
fn main() {
let black = Color(0, 0, 0);
let origin = Point(0, 0, 0);
println!("Black: ({}, {}, {})", black.0, black.1, black.2);
}Unit-Like Structs
Structs without any fields:
struct AlwaysEqual;
fn main() {
let subject = AlwaysEqual;
}Method Syntax
Defining Methods
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}
fn width(&self) -> bool {
self.width > 0
}
fn can_hold(&self, other: &Rectangle) -> bool {
self.width > other.width && self.height > other.height
}
}
fn main() {
let rect1 = Rectangle {
width: 30,
height: 50,
};
let rect2 = Rectangle {
width: 10,
height: 40,
};
println!("The area of the rectangle is {} square pixels.", rect1.area());
println!("Can rect1 hold rect2? {}", rect1.can_hold(&rect2));
}Associated Functions
Functions that don't take self as a parameter:
impl Rectangle {
fn square(size: u32) -> Self {
Self {
width: size,
height: size,
}
}
}
fn main() {
let sq = Rectangle::square(3);
}Multiple impl Blocks
impl Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}
}
impl Rectangle {
fn can_hold(&self, other: &Rectangle) -> bool {
self.width > other.width && self.height > other.height
}
}Practical Examples
Example 1: Person Struct
#[derive(Debug)]
struct Person {
first_name: String,
last_name: String,
age: u32,
}
impl Person {
fn new(first_name: String, last_name: String, age: u32) -> Self {
Self {
first_name,
last_name,
age,
}
}
fn full_name(&self) -> String {
format!("{} {}", self.first_name, self.last_name)
}
fn is_adult(&self) -> bool {
self.age >= 18
}
fn have_birthday(&mut self) {
self.age += 1;
}
}
fn main() {
let mut person = Person::new(
String::from("John"),
String::from("Doe"),
25
);
println!("Full name: {}", person.full_name());
println!("Is adult: {}", person.is_adult());
person.have_birthday();
println!("New age: {}", person.age);
}Example 2: Bank Account
#[derive(Debug)]
struct BankAccount {
account_number: String,
holder_name: String,
balance: f64,
}
impl BankAccount {
fn new(account_number: String, holder_name: String) -> Self {
Self {
account_number,
holder_name,
balance: 0.0,
}
}
fn deposit(&mut self, amount: f64) {
if amount > 0.0 {
self.balance += amount;
println!("Deposited ${:.2}. New balance: ${:.2}", amount, self.balance);
}
}
fn withdraw(&mut self, amount: f64) -> bool {
if amount > 0.0 && amount <= self.balance {
self.balance -= amount;
println!("Withdrew ${:.2}. New balance: ${:.2}", amount, self.balance);
true
} else {
println!("Insufficient funds or invalid amount");
false
}
}
fn get_balance(&self) -> f64 {
self.balance
}
}
fn main() {
let mut account = BankAccount::new(
String::from("123456789"),
String::from("Alice Smith")
);
account.deposit(1000.0);
account.withdraw(250.0);
println!("Final balance: ${:.2}", account.get_balance());
}Ownership and Structs
Structs with References (Preview)
struct User<'a> {
username: &'a str,
email: &'a str,
sign_in_count: u64,
active: bool,
}Note: This requires lifetimes, which we'll cover in Chapter 13.
Debugging Structs
Using Debug Trait
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
fn main() {
let rect1 = Rectangle {
width: 30,
height: 50,
};
println!("rect1 is {:?}", rect1);
println!("rect1 is {:#?}", rect1); // Pretty print
}Practice Exercises
Exercise 1: Student Grade System
#[derive(Debug)]
struct Student {
name: String,
grades: Vec<f64>,
}
impl Student {
fn new(name: String) -> Self {
// Your implementation
}
fn add_grade(&mut self, grade: f64) {
// Your implementation
}
fn average_grade(&self) -> f64 {
// Your implementation
}
fn letter_grade(&self) -> char {
// Your implementation
}
}Exercise 2: Point and Distance
#[derive(Debug)]
struct Point {
x: f64,
y: f64,
}
impl Point {
fn new(x: f64, y: f64) -> Self {
// Your implementation
}
fn distance_from_origin(&self) -> f64 {
// Your implementation
}
fn distance_from(&self, other: &Point) -> f64 {
// Your implementation
}
}Common Patterns
Builder Pattern (Preview)
struct User {
name: String,
email: String,
age: Option<u32>,
}
impl User {
fn builder() -> UserBuilder {
UserBuilder::default()
}
}
#[derive(Default)]
struct UserBuilder {
name: Option<String>,
email: Option<String>,
age: Option<u32>,
}
impl UserBuilder {
fn name(mut self, name: String) -> Self {
self.name = Some(name);
self
}
fn email(mut self, email: String) -> Self {
self.email = Some(email);
self
}
fn age(mut self, age: u32) -> Self {
self.age = Some(age);
self
}
fn build(self) -> Result<User, &'static str> {
Ok(User {
name: self.name.ok_or("Name is required")?,
email: self.email.ok_or("Email is required")?,
age: self.age,
})
}
}Key Takeaways
- ā Structs group related data together
- ā
Use
implblocks to define methods and associated functions - ā
Methods take
&self,&mut self, orselfas first parameter - ā
Associated functions don't take
self(like constructors) - ā
#[derive(Debug)]enables debug printing - ā Field init shorthand reduces repetition
Ready for Chapter 7? ā Enums and Pattern Matching