📘 PHẦN 3: STRUCTS VÀ ENUMS
🎯 Mục tiêu tổng quát
- Nắm vững cách định nghĩa và sử dụng structs
- Implement methods và associated functions
- Hiểu và sử dụng enums hiệu quả
- Master pattern matching với match, if let, while let
- Làm việc với Option
<T>và Result<T, E> - Apply advanced patterns và destructuring
- Xây dựng ứng dụng với data structures phức tạp
🧑🏫 Bài 1: Structs cơ bản
Định nghĩa và khởi tạo
Classic struct:
rust
struct User {
username: String,
email: String,
age: u8,
active: bool,
}
fn main() {
let user1 = User {
username: String::from("alice123"),
email: String::from("[email protected]"),
age: 25,
active: true,
};
println!("Username: {}", user1.username);
println!("Email: {}", user1.email);
}Mutable struct:
rust
fn main() {
let mut user = User {
username: String::from("bob456"),
email: String::from("[email protected]"),
age: 30,
active: true,
};
// Modify field
user.email = String::from("[email protected]");
user.age += 1;
println!("New email: {}", user.email);
}Field init shorthand
rust
fn build_user(username: String, email: String) -> User {
User {
username, // Shorthand instead of username: username
email, // Shorthand instead of email: email
age: 0,
active: true,
}
}
fn main() {
let user = build_user(
String::from("charlie"),
String::from("[email protected]")
);
println!("User: {}", user.username);
}Struct update syntax
rust
fn main() {
let user1 = User {
username: String::from("alice"),
email: String::from("[email protected]"),
age: 25,
active: true,
};
// Create user2 based on user1
let user2 = User {
email: String::from("[email protected]"),
..user1 // Copy remaining fields from user1
};
// Note: user1.username and user1.email moved to user2
// println!("{}", user1.username); // ERROR: moved
println!("Age from user1: {}", user1.age); // OK: Copy trait
}Tuple structs
rust
struct Color(u8, u8, u8);
struct Point(i32, i32, i32);
fn main() {
let black = Color(0, 0, 0);
let origin = Point(0, 0, 0);
// Access by index
println!("Red: {}", black.0);
println!("X: {}", origin.0);
// Destructure
let Color(r, g, b) = black;
println!("RGB: ({}, {}, {})", r, g, b);
}Unit-like structs
rust
struct AlwaysEqual;
fn main() {
let subject = AlwaysEqual;
// Useful for implementing traits without data
}Ownership trong structs
Storing references requires lifetimes:
rust
// This won't compile without lifetimes
// struct User {
// username: &str, // ERROR: missing lifetime
// email: &str,
// }
// Correct with lifetime
struct User<'a> {
username: &'a str,
email: &'a str,
}
fn main() {
let name = String::from("alice");
let email = String::from("[email protected]");
let user = User {
username: &name,
email: &email,
};
println!("{}", user.username);
}Prefer owned types:
rust
#[derive(Debug)]
struct User {
username: String, // Owned
email: String, // Owned
age: u8,
}
fn main() {
let user = User {
username: String::from("bob"),
email: String::from("[email protected]"),
age: 30,
};
// Debug print
println!("{:?}", user);
// Pretty print
println!("{:#?}", user);
}🧑🏫 Bài 2: Methods và Associated Functions
Defining methods
rust
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
// Method takes &self
fn area(&self) -> u32 {
self.width * self.height
}
fn perimeter(&self) -> u32 {
2 * (self.width + self.height)
}
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!("Area: {}", rect1.area());
println!("Perimeter: {}", rect1.perimeter());
println!("Can hold: {}", rect1.can_hold(&rect2));
}Self parameters
rust
impl Rectangle {
// Immutable borrow
fn area(&self) -> u32 {
self.width * self.height
}
// Mutable borrow
fn scale(&mut self, factor: u32) {
self.width *= factor;
self.height *= factor;
}
// Takes ownership (rare)
fn into_square(self) -> Rectangle {
let size = self.width.max(self.height);
Rectangle {
width: size,
height: size,
}
}
}
fn main() {
let rect = Rectangle { width: 10, height: 20 };
println!("Area: {}", rect.area());
let mut rect2 = Rectangle { width: 5, height: 10 };
rect2.scale(2);
println!("After scale: {:?}", rect2);
let rect3 = Rectangle { width: 15, height: 25 };
let square = rect3.into_square();
// println!("{:?}", rect3); // ERROR: rect3 moved
println!("Square: {:?}", square);
}Multiple impl blocks
rust
impl Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}
}
impl Rectangle {
fn perimeter(&self) -> u32 {
2 * (self.width + self.height)
}
}
// Can split implementations for organizationAssociated functions
rust
impl Rectangle {
// Associated function (constructor)
fn new(width: u32, height: u32) -> Rectangle {
Rectangle { width, height }
}
fn square(size: u32) -> Rectangle {
Rectangle {
width: size,
height: size,
}
}
}
fn main() {
let rect = Rectangle::new(30, 50);
let square = Rectangle::square(25);
println!("Rectangle: {:?}", rect);
println!("Square: {:?}", square);
}Method chaining
rust
#[derive(Debug)]
struct Builder {
width: u32,
height: u32,
color: String,
}
impl Builder {
fn new() -> Self {
Builder {
width: 0,
height: 0,
color: String::from("white"),
}
}
fn width(mut self, width: u32) -> Self {
self.width = width;
self
}
fn height(mut self, height: u32) -> Self {
self.height = height;
self
}
fn color(mut self, color: &str) -> Self {
self.color = String::from(color);
self
}
fn build(self) -> Self {
self
}
}
fn main() {
let builder = Builder::new()
.width(100)
.height(200)
.color("blue")
.build();
println!("{:#?}", builder);
}🧑🏫 Bài 3: Enums và Pattern Matching
Defining enums
rust
enum IpAddrKind {
V4,
V6,
}
fn main() {
let four = IpAddrKind::V4;
let six = IpAddrKind::V6;
route(four);
route(six);
}
fn route(ip_kind: IpAddrKind) {
// Use the enum
}Enums với data
rust
#[derive(Debug)]
enum IpAddr {
V4(u8, u8, u8, u8),
V6(String),
}
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(u8, u8, u8),
}
fn main() {
let home = IpAddr::V4(127, 0, 0, 1);
let loopback = IpAddr::V6(String::from("::1"));
println!("{:?}", home);
println!("{:?}", loopback);
let msg1 = Message::Quit;
let msg2 = Message::Move { x: 10, y: 20 };
let msg3 = Message::Write(String::from("Hello"));
let msg4 = Message::ChangeColor(255, 0, 0);
}Methods on enums:
rust
impl Message {
fn call(&self) {
match self {
Message::Quit => println!("Quit"),
Message::Move { x, y } => println!("Move to ({}, {})", x, y),
Message::Write(text) => println!("Write: {}", text),
Message::ChangeColor(r, g, b) => println!("Color: RGB({}, {}, {})", r, g, b),
}
}
}
fn main() {
let msg = Message::Write(String::from("Hello"));
msg.call();
}Match expression
rust
enum Coin {
Penny,
Nickel,
Dime,
Quarter,
}
fn value_in_cents(coin: Coin) -> u8 {
match coin {
Coin::Penny => 1,
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter => 25,
}
}
fn main() {
let coin = Coin::Quarter;
println!("Value: {} cents", value_in_cents(coin));
}Match with data:
rust
#[derive(Debug)]
enum UsState {
Alabama,
Alaska,
// ... etc
}
enum Coin {
Penny,
Nickel,
Dime,
Quarter(UsState),
}
fn value_in_cents(coin: Coin) -> u8 {
match coin {
Coin::Penny => {
println!("Lucky penny!");
1
}
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter(state) => {
println!("State quarter from {:?}!", state);
25
}
}
}Catch-all:
rust
fn main() {
let number = 7;
match number {
1 => println!("One"),
2 => println!("Two"),
3 => println!("Three"),
_ => println!("Other"), // Catch-all
}
// Or use the value
match number {
1 | 2 | 3 => println!("Small"),
n @ 4..=9 => println!("Medium: {}", n),
_ => println!("Large"),
}
}If let
rust
fn main() {
let some_value = Some(3);
// With match
match some_value {
Some(3) => println!("three"),
_ => (),
}
// With if let (more concise)
if let Some(3) = some_value {
println!("three");
}
// With else
if let Some(value) = some_value {
println!("Got value: {}", value);
} else {
println!("No value");
}
}While let
rust
fn main() {
let mut stack = Vec::new();
stack.push(1);
stack.push(2);
stack.push(3);
while let Some(top) = stack.pop() {
println!("{}", top);
}
}Match guards
rust
fn main() {
let num = Some(4);
match num {
Some(x) if x < 5 => println!("Less than five: {}", x),
Some(x) => println!("{}", x),
None => (),
}
let x = 4;
let y = false;
match x {
4 | 5 | 6 if y => println!("yes"),
_ => println!("no"),
}
}🧑🏫 Bài 4: Option và Result
Option<T>
rust
fn main() {
let some_number = Some(5);
let some_string = Some("a string");
let absent_number: Option<i32> = None;
// Must handle None case
match some_number {
Some(n) => println!("Number: {}", n),
None => println!("No number"),
}
}Why Option instead of null?
rust
// Rust doesn't have null!
// Option forces you to handle the None case
fn divide(a: f64, b: f64) -> Option<f64> {
if b == 0.0 {
None
} else {
Some(a / b)
}
}
fn main() {
match divide(10.0, 2.0) {
Some(result) => println!("Result: {}", result),
None => println!("Cannot divide by zero"),
}
}Working with Option
rust
fn main() {
let x = Some(5);
let y: Option<i32> = None;
// unwrap - panic if None
// println!("{}", y.unwrap()); // PANIC!
// unwrap_or - provide default
println!("{}", x.unwrap_or(0)); // 5
println!("{}", y.unwrap_or(0)); // 0
// unwrap_or_else - compute default
let result = y.unwrap_or_else(|| {
println!("Computing default");
42
});
// expect - panic with custom message
// let value = y.expect("Expected a value!"); // PANIC with message
// is_some / is_none
if x.is_some() {
println!("x has a value");
}
if y.is_none() {
println!("y is None");
}
// map
let x = Some(5);
let y = x.map(|n| n * 2);
println!("{:?}", y); // Some(10)
// and_then
let result = Some(4).and_then(|n| {
if n < 5 {
Some(n * 2)
} else {
None
}
});
println!("{:?}", result); // Some(8)
}Result<T, E>
rust
use std::fs::File;
use std::io::ErrorKind;
fn main() {
let file_result = File::open("hello.txt");
let file = match file_result {
Ok(file) => file,
Err(error) => match error.kind() {
ErrorKind::NotFound => {
println!("File not found");
return;
}
other_error => {
panic!("Problem opening file: {:?}", other_error);
}
}
};
}Shortcuts:
rust
use std::fs::File;
fn main() {
// unwrap - panic if error
// let file = File::open("hello.txt").unwrap();
// expect - panic with message
let file = File::open("hello.txt")
.expect("Failed to open hello.txt");
}Error propagation
The ? operator:
rust
use std::fs::File;
use std::io::{self, Read};
fn read_username_from_file() -> Result<String, io::Error> {
let mut file = File::open("username.txt")?;
let mut username = String::new();
file.read_to_string(&mut username)?;
Ok(username)
}
// Even shorter
fn read_username_short() -> Result<String, io::Error> {
let mut username = String::new();
File::open("username.txt")?.read_to_string(&mut username)?;
Ok(username)
}
// Shortest
fn read_username_shortest() -> Result<String, io::Error> {
std::fs::read_to_string("username.txt")
}
fn main() {
match read_username_from_file() {
Ok(username) => println!("Username: {}", username),
Err(e) => println!("Error: {}", e),
}
}? in main:
rust
use std::error::Error;
use std::fs::File;
fn main() -> Result<(), Box<dyn Error>> {
let file = File::open("hello.txt")?;
Ok(())
}Custom error types
rust
use std::fmt;
#[derive(Debug)]
enum MathError {
DivisionByZero,
NegativeSquareRoot,
}
impl fmt::Display for MathError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
MathError::DivisionByZero => write!(f, "Cannot divide by zero"),
MathError::NegativeSquareRoot => write!(f, "Cannot take square root of negative number"),
}
}
}
fn divide(a: f64, b: f64) -> Result<f64, MathError> {
if b == 0.0 {
Err(MathError::DivisionByZero)
} else {
Ok(a / b)
}
}
fn sqrt(x: f64) -> Result<f64, MathError> {
if x < 0.0 {
Err(MathError::NegativeSquareRoot)
} else {
Ok(x.sqrt())
}
}
fn main() {
match divide(10.0, 2.0) {
Ok(result) => println!("Result: {}", result),
Err(e) => println!("Error: {}", e),
}
match sqrt(-4.0) {
Ok(result) => println!("Result: {}", result),
Err(e) => println!("Error: {}", e),
}
}🧑🏫 Bài 5: Advanced Patterns
Destructuring
Structs:
rust
struct Point {
x: i32,
y: i32,
}
fn main() {
let p = Point { x: 0, y: 7 };
let Point { x, y } = p;
println!("x: {}, y: {}", x, y);
// Rename
let Point { x: a, y: b } = p;
println!("a: {}, b: {}", a, b);
// Match specific values
match p {
Point { x: 0, y } => println!("On y axis at {}", y),
Point { x, y: 0 } => println!("On x axis at {}", x),
Point { x, y } => println!("At ({}, {})", x, y),
}
}Enums:
rust
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
}
fn process_message(msg: Message) {
match msg {
Message::Quit => println!("Quit"),
Message::Move { x, y } => println!("Move to ({}, {})", x, y),
Message::Write(text) => println!("Write: {}", text),
}
}Nested destructuring:
rust
enum Color {
Rgb(u8, u8, u8),
Hsv(u8, u8, u8),
}
enum Message {
Quit,
ChangeColor(Color),
}
fn main() {
let msg = Message::ChangeColor(Color::Rgb(255, 0, 0));
match msg {
Message::Quit => println!("Quit"),
Message::ChangeColor(Color::Rgb(r, g, b)) => {
println!("RGB: ({}, {}, {})", r, g, b);
}
Message::ChangeColor(Color::Hsv(h, s, v)) => {
println!("HSV: ({}, {}, {})", h, s, v);
}
}
}Ignoring values
rust
fn main() {
let numbers = (1, 2, 3, 4, 5);
// Ignore some values
match numbers {
(first, _, third, _, fifth) => {
println!("First: {}, Third: {}, Fifth: {}", first, third, fifth);
}
}
// Ignore remaining
let (first, second, ..) = numbers;
println!("{}, {}", first, second);
// Ignore with _name (useful for debugging)
let (_x, y) = (1, 2);
println!("y: {}", y);
}Match ranges
rust
fn main() {
let x = 5;
match x {
1..=5 => println!("1 through 5"),
_ => println!("Something else"),
}
let c = 'c';
match c {
'a'..='j' => println!("Early ASCII letter"),
'k'..='z' => println!("Late ASCII letter"),
_ => println!("Something else"),
}
}@ bindings
rust
enum Message {
Hello { id: i32 },
}
fn main() {
let msg = Message::Hello { id: 5 };
match msg {
Message::Hello { id: id_variable @ 3..=7 } => {
println!("Found an id in range: {}", id_variable);
}
Message::Hello { id: 10..=12 } => {
println!("Found an id in another range");
}
Message::Hello { id } => {
println!("Found some other id: {}", id);
}
}
}Refutability
Irrefutable patterns (always match):
rust
fn main() {
let x = 5; // Irrefutable
let (x, y, z) = (1, 2, 3); // Irrefutable
}Refutable patterns (can fail to match):
rust
fn main() {
let some_value = Some(5);
// if let with refutable pattern
if let Some(x) = some_value {
println!("{}", x);
}
// This won't compile:
// let Some(x) = some_value; // ERROR: refutable pattern in let
// Use if let or match instead
}🧪 BÀI TẬP LỚN CUỐI PHẦN: Hệ thống quản lý sản phẩm
Mô tả bài toán
Xây dựng hệ thống quản lý cửa hàng bán lẻ với nhiều loại sản phẩm:
- Electronics (laptop, phone, tablet)
- Clothing (shirt, pants, shoes)
- Food (fresh, packaged, frozen)
Mỗi loại có thuộc tính riêng và cách tính giá khác nhau.
Yêu cầu
1. Data structures:
rust
#[derive(Debug, Clone)]
enum ProductCategory {
Electronics {
brand: String,
warranty_months: u8,
},
Clothing {
size: String,
material: String,
},
Food {
expiry_date: String,
storage_temp: i8,
},
}
#[derive(Debug, Clone)]
struct Product {
id: u32,
name: String,
category: ProductCategory,
base_price: f64,
quantity: u32,
}
enum DiscountType {
Percentage(f64),
FixedAmount(f64),
BuyOneGetOne,
}
struct Cart {
items: Vec<(Product, u32)>, // (product, quantity)
}
enum PaymentMethod {
Cash,
CreditCard { number: String },
MobilePayment { phone: String },
}
struct Transaction {
id: u32,
products: Vec<(Product, u32)>,
total: f64,
payment: PaymentMethod,
date: String,
}2. Core functionality:
rust
impl Product {
fn new(id: u32, name: String, category: ProductCategory, base_price: f64) -> Self;
fn calculate_price(&self, discount: Option<DiscountType>) -> f64;
fn is_available(&self) -> bool;
fn add_stock(&mut self, quantity: u32);
fn remove_stock(&mut self, quantity: u32) -> Result<(), String>;
}
impl Cart {
fn new() -> Self;
fn add_item(&mut self, product: Product, quantity: u32) -> Result<(), String>;
fn remove_item(&mut self, product_id: u32) -> Result<(), String>;
fn calculate_total(&self) -> f64;
fn checkout(&self, payment: PaymentMethod) -> Result<Transaction, String>;
}3. Pattern matching requirements:
- Match on ProductCategory để tính giá đặc biệt
- Match on DiscountType để apply discount
- Match on PaymentMethod để xử lý thanh toán
- Use Option<> cho optional fields
- Use Result<> cho error handling
Template code:
rust
#[derive(Debug, Clone)]
enum ProductCategory {
Electronics {
brand: String,
warranty_months: u8,
},
Clothing {
size: String,
material: String,
},
Food {
expiry_date: String,
storage_temp: i8,
},
}
#[derive(Debug, Clone)]
struct Product {
id: u32,
name: String,
category: ProductCategory,
base_price: f64,
quantity: u32,
}
impl Product {
fn new(id: u32, name: String, category: ProductCategory, base_price: f64, quantity: u32) -> Self {
Product {
id,
name,
category,
base_price,
quantity,
}
}
fn calculate_price(&self, discount: Option<DiscountType>) -> f64 {
let mut price = self.base_price;
// Category-based pricing
match &self.category {
ProductCategory::Electronics { warranty_months, .. } => {
price += (*warranty_months as f64) * 2.0; // Add warranty cost
}
ProductCategory::Clothing { material, .. } => {
if material == "premium" {
price *= 1.5;
}
}
ProductCategory::Food { storage_temp, .. } => {
if *storage_temp < 0 {
price *= 1.2; // Frozen food markup
}
}
}
// Apply discount
if let Some(disc) = discount {
match disc {
DiscountType::Percentage(percent) => {
price *= 1.0 - (percent / 100.0);
}
DiscountType::FixedAmount(amount) => {
price -= amount;
if price < 0.0 {
price = 0.0;
}
}
DiscountType::BuyOneGetOne => {
// Implement BOGO logic
price *= 0.5;
}
}
}
price
}
fn is_available(&self) -> bool {
self.quantity > 0
}
fn display(&self) {
println!("ID: {}", self.id);
println!("Name: {}", self.name);
println!("Price: ${:.2}", self.base_price);
println!("Quantity: {}", self.quantity);
match &self.category {
ProductCategory::Electronics { brand, warranty_months } => {
println!("Category: Electronics");
println!("Brand: {}", brand);
println!("Warranty: {} months", warranty_months);
}
ProductCategory::Clothing { size, material } => {
println!("Category: Clothing");
println!("Size: {}", size);
println!("Material: {}", material);
}
ProductCategory::Food { expiry_date, storage_temp } => {
println!("Category: Food");
println!("Expiry: {}", expiry_date);
println!("Storage: {}°C", storage_temp);
}
}
}
}
#[derive(Debug)]
enum DiscountType {
Percentage(f64),
FixedAmount(f64),
BuyOneGetOne,
}
struct Cart {
items: Vec<(Product, u32)>,
}
impl Cart {
fn new() -> Self {
Cart { items: Vec::new() }
}
fn add_item(&mut self, product: Product, quantity: u32) -> Result<(), String> {
if !product.is_available() {
return Err(String::from("Product out of stock"));
}
if quantity > product.quantity {
return Err(format!("Only {} items available", product.quantity));
}
self.items.push((product, quantity));
Ok(())
}
fn calculate_total(&self) -> f64 {
self.items.iter()
.map(|(product, quantity)| {
product.calculate_price(None) * (*quantity as f64)
})
.sum()
}
fn display(&self) {
println!("\n=== Shopping Cart ===");
for (product, quantity) in &self.items {
println!("{} x {} - ${:.2}",
product.name,
quantity,
product.calculate_price(None) * (*quantity as f64)
);
}
println!("Total: ${:.2}", self.calculate_total());
}
}
fn main() {
// Create products
let laptop = Product::new(
1,
String::from("Gaming Laptop"),
ProductCategory::Electronics {
brand: String::from("Dell"),
warranty_months: 24,
},
1000.0,
5,
);
let shirt = Product::new(
2,
String::from("Cotton Shirt"),
ProductCategory::Clothing {
size: String::from("L"),
material: String::from("cotton"),
},
50.0,
20,
);
let milk = Product::new(
3,
String::from("Fresh Milk"),
ProductCategory::Food {
expiry_date: String::from("2024-12-31"),
storage_temp: 4,
},
5.0,
50,
);
// Display products
laptop.display();
println!();
shirt.display();
println!();
milk.display();
// Create cart
let mut cart = Cart::new();
// Add items
match cart.add_item(laptop.clone(), 1) {
Ok(_) => println!("\nAdded laptop to cart"),
Err(e) => println!("Error: {}", e),
}
match cart.add_item(shirt.clone(), 2) {
Ok(_) => println!("Added shirts to cart"),
Err(e) => println!("Error: {}", e),
}
cart.display();
// TODO: Implement remaining features
}Yêu cầu mở rộng:
Inventory Management:
- Stock tracking
- Low stock alerts
- Reorder automation
Advanced Discounts:
- Bulk discounts
- Seasonal sales
- Member discounts
- Coupon codes
Transaction History:
- Save transactions
- Generate receipts
- Sales reports
- Revenue analytics
Search & Filter:
- Search by name/category
- Filter by price range
- Sort by various criteria
Payment Processing:
- Multiple payment methods
- Payment validation
- Refund handling
Export/Import:
- Save to JSON/CSV
- Load from file
- Backup system
