Skip to content

Data Modeling - Structs & Enums ADT

Định nghĩa custom types — Kết hợp Ownership với Type System

Named-Field Structs (Phổ biến nhất)

rust
struct User {
    username: String,
    email: String,
    sign_in_count: u64,
    active: bool,
}

fn main() {
    let user1 = User {
        email: String::from("someone@example.com"),
        username: String::from("someusername123"),
        active: true,
        sign_in_count: 1,
    };
    
    println!("Username: {}", user1.username);
}

Memory Layout

Struct User Memory Layout:
═══════════════════════════════════════════════════════════════════

                    STACK                              HEAP
               ┌─────────────────┐            
    user1:     │ username: String│            ┌─────────────────────┐
               │   ptr ──────────┼───────────▶│s│o│m│e│u│s│e│r│...│
               │   len: 15       │            └─────────────────────┘
               │   cap: 15       │
               ├─────────────────┤            ┌─────────────────────┐
               │ email: String   │            │s│o│m│e│o│n│e│@│...│
               │   ptr ──────────┼───────────▶└─────────────────────┘
               │   len: 19       │
               │   cap: 19       │
               ├─────────────────┤
               │ sign_in_count   │
               │   = 1 (u64)     │  8 bytes
               ├─────────────────┤
               │ active: bool    │
               │   = true        │  1 byte + padding
               └─────────────────┘
               
    Total stack: 24 + 24 + 8 + 8 = 64 bytes (với alignment)

Mutable Struct

rust
fn main() {
    let mut user1 = User {
        email: String::from("someone@example.com"),
        username: String::from("someusername123"),
        active: true,
        sign_in_count: 1,
    };
    
    user1.email = String::from("another@example.com");  // ✅ OK
}

⚠️ TOÀN BỘ STRUCT phải mutable

Rust không cho phép chỉ một số fields mutable. Nếu cần mutability có chọn lọc, dùng Cell hoặc RefCell (Interior Mutability).

Struct Update Syntax

rust
fn main() {
    let user1 = User {
        email: String::from("someone@example.com"),
        username: String::from("someusername123"),
        active: true,
        sign_in_count: 1,
    };
    
    // Tạo user2 với một số fields từ user1
    let user2 = User {
        email: String::from("another@example.com"),
        ..user1  // Còn lại lấy từ user1
    };
    
    // ⚠️ CẢNH BÁO: user1.username đã bị MOVE sang user2!
    // println!("{}", user1.username);  // ❌ ERROR
    println!("{}", user1.email);        // ✅ OK - email không bị move
    println!("{}", user1.active);       // ✅ OK - bool là Copy
}

Tuple Structs

Struct không có field names — hữu ích cho newtype pattern:

rust
struct Color(i32, i32, i32);
struct Point(i32, i32, i32);

fn main() {
    let black = Color(0, 0, 0);
    let origin = Point(0, 0, 0);
    
    // Color và Point là KHÁC TYPE dù có cùng fields
    // let c: Color = origin;  // ❌ ERROR: type mismatch
    
    println!("R: {}", black.0);  // Access bằng index
}

Unit Structs

Struct không có fields — dùng làm marker types:

rust
struct AlwaysEqual;

fn main() {
    let subject = AlwaysEqual;
    // Thường dùng với traits, sẽ học sau
}

Enums: Algebraic Data Types

Basic Enum

rust
enum IpAddrKind {
    V4,
    V6,
}

fn main() {
    let four = IpAddrKind::V4;
    let six = IpAddrKind::V6;
    
    route(four);
    route(six);
}

fn route(ip_kind: IpAddrKind) {
    // ...
}

Enum với Data (The Power!)

rust
enum IpAddr {
    V4(u8, u8, u8, u8),  // Tuple variant
    V6(String),          // String variant
}

fn main() {
    let home = IpAddr::V4(127, 0, 0, 1);
    let loopback = IpAddr::V6(String::from("::1"));
}

Memory Layout: Enum

enum IpAddr {
    V4(u8, u8, u8, u8),  // 4 bytes data
    V6(String),          // 24 bytes data (ptr + len + cap)
}
═══════════════════════════════════════════════════════════════════

IpAddr::V4(127, 0, 0, 1):
┌─────────────────────────────────────┐
│ discriminant: 0 (tag for V4)        │  8 bytes (padding)
├─────────────────────────────────────┤
│ 127 │  0  │  0  │  1  │  padding    │  24 bytes (match largest variant)
└─────────────────────────────────────┘

IpAddr::V6("::1"):
┌─────────────────────────────────────┐
│ discriminant: 1 (tag for V6)        │  8 bytes
├─────────────────────────────────────┤
│ ptr ────────────────────────────────┼──▶ "::1" (heap)
│ len: 3                              │
│ cap: 3                              │  24 bytes
└─────────────────────────────────────┘

Total size: 32 bytes (8 tag + 24 data, aligned)

Enum với Named Fields

rust
enum Message {
    Quit,                           // Unit variant
    Move { x: i32, y: i32 },        // Struct-like variant
    Write(String),                  // Tuple variant
    ChangeColor(i32, i32, i32),     // Tuple variant
}

impl Message {
    fn call(&self) {
        // Method trên enum
    }
}

fn main() {
    let m = Message::Write(String::from("hello"));
    m.call();
}

Option<T> — Null Safety

Vấn đề với Null trong các ngôn ngữ khác

java
// Java - NullPointerException tại runtime
String s = null;
int len = s.length();  // 💥 CRASH!

Rust: Không có Null!

rust
// Rust - Không tồn tại null
// let s: String = null;  // ❌ Concept không tồn tại

Option<T> thay thế Null

rust
enum Option<T> {
    Some(T),  // Có giá trị
    None,     // Không có giá trị (thay cho null)
}
rust
fn main() {
    let some_number: Option<i32> = Some(5);
    let no_number: Option<i32> = None;
    
    // ❌ KHÔNG THỂ dùng Option\<T> như T trực tiếp
    // let x: i32 = some_number;  // ERROR: type mismatch
    
    // ✅ PHẢI explicitly handle None case
    match some_number {
        Some(n) => println!("Got: {}", n),
        None => println!("Nothing"),
    }
}

Compiler bắt buộc handle None

rust
fn main() {
    let x: Option<i32> = Some(5);
    let y: i32 = 10;
    
    // let sum = x + y;  // ❌ ERROR: cannot add Option\<i32> + i32
    
    // ✅ ĐÚNG: Extract value first
    let sum = match x {
        Some(n) => n + y,
        None => y,  // Default khi None
    };
    
    println!("Sum: {}", sum);
}

Common Option Methods

rust
fn main() {
    let x: Option<i32> = Some(5);
    let y: Option<i32> = None;
    
    // unwrap() - Panic nếu None (chỉ dùng khi chắc chắn có value)
    let val = x.unwrap();  // 5
    // y.unwrap();  // 💥 PANIC!
    
    // unwrap_or() - Default value
    let val = y.unwrap_or(0);  // 0
    
    // is_some(), is_none()
    if x.is_some() {
        println!("Has value");
    }
    
    // map() - Transform nếu Some
    let doubled = x.map(|n| n * 2);  // Some(10)
    
    // and_then() - Chain operations
    let result = x
        .map(|n| n * 2)
        .and_then(|n| if n > 5 { Some(n) } else { None });
}

Result<T, E> — Error Handling

Definition

rust
enum Result<T, E> {
    Ok(T),   // Success với value T
    Err(E),  // Failure với error E
}

Ví dụ: File Operations

rust
use std::fs::File;

fn main() {
    let f: Result<File, std::io::Error> = File::open("hello.txt");
    
    let file = match f {
        Ok(file) => file,
        Err(error) => {
            panic!("Problem opening file: {:?}", error);
        }
    };
}

Compiler bắt buộc handle Error

rust
fn main() {
    let f = File::open("hello.txt");
    
    // ❌ KHÔNG THỂ ignore Result
    // f.read_to_string(...);  // ERROR: Result must be used
    
    // ✅ PHẢI handle
    match f {
        Ok(mut file) => {
            let mut contents = String::new();
            file.read_to_string(&mut contents).unwrap();
        }
        Err(e) => println!("Error: {}", e),
    }
}

The ? Operator — Error Propagation

rust
use std::fs::File;
use std::io::{self, Read};

fn read_file() -> Result<String, io::Error> {
    let mut f = File::open("hello.txt")?;  // Return Err nếu fail
    let mut s = String::new();
    f.read_to_string(&mut s)?;             // Return Err nếu fail
    Ok(s)
}

// Equivalent to:
fn read_file_verbose() -> Result<String, io::Error> {
    let mut f = match File::open("hello.txt") {
        Ok(file) => file,
        Err(e) => return Err(e),
    };
    let mut s = String::new();
    match f.read_to_string(&mut s) {
        Ok(_) => Ok(s),
        Err(e) => Err(e),
    }
}

Pattern Matching với match

Exhaustiveness — Compiler bắt buộc handle ALL cases

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,
        // ❌ COMPILE ERROR: non-exhaustive patterns
    }
}

Compiler Error:

error[E0004]: non-exhaustive patterns: `Quarter` not covered
 --> src/main.rs:9:11
  |
9 |     match coin {
  |           ^^^^ pattern `Quarter` not covered
}

Handle ALL cases

rust
fn value_in_cents(coin: Coin) -> u8 {
    match coin {
        Coin::Penny => 1,
        Coin::Nickel => 5,
        Coin::Dime => 10,
        Coin::Quarter => 25,  // ✅ Now exhaustive
    }
}

Catch-all với _ hoặc variable

rust
fn value_in_cents(coin: Coin) -> u8 {
    match coin {
        Coin::Penny => 1,
        Coin::Nickel => 5,
        _ => 0,  // Catch-all: Dime và Quarter → 0
    }
}

// Hoặc bind to variable
fn describe_coin(coin: Coin) -> String {
    match coin {
        Coin::Penny => String::from("Lucky penny!"),
        other => format!("Some other coin: {:?}", other),
    }
}

Pattern Matching với Data

rust
enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
    ChangeColor(i32, i32, i32),
}

fn process(msg: Message) {
    match msg {
        Message::Quit => {
            println!("Quit");
        }
        Message::Move { x, y } => {  // Destructure struct fields
            println!("Move to ({}, {})", x, y);
        }
        Message::Write(text) => {    // Destructure tuple variant
            println!("Text: {}", text);
        }
        Message::ChangeColor(r, g, b) => {
            println!("Color: ({}, {}, {})", r, g, b);
        }
    }
}

Guards trong match

rust
fn main() {
    let num = Some(4);
    
    match num {
        Some(x) if x < 5 => println!("Less than five: {}", x),
        Some(x) => println!("x = {}", x),
        None => println!("None"),
    }
}

if let — Syntax Sugar

Khi chỉ quan tâm một pattern

rust
// Verbose match
let config_max = Some(3u8);
match config_max {
    Some(max) => println!("Max: {}", max),
    _ => (),  // Bỏ qua None
}

// ✅ Cleaner với if let
if let Some(max) = config_max {
    println!("Max: {}", max);
}

if let với else

rust
let coin = Coin::Penny;

// match version
match coin {
    Coin::Quarter => println!("Quarter!"),
    _ => println!("Not a quarter"),
}

// if let version
if let Coin::Quarter = coin {
    println!("Quarter!");
} else {
    println!("Not a quarter");
}

while let — Loop while pattern matches

rust
let mut stack = vec![1, 2, 3];

while let Some(top) = stack.pop() {
    println!("{}", top);
}
// Output: 3, 2, 1 (then loop ends when pop() returns None)

Ownership trong Structs & Enums

Struct owns its data

rust
struct User {
    username: String,  // User owns this String
    email: String,     // User owns this String
}

fn main() {
    let user = User {
        username: String::from("alice"),
        email: String::from("alice@example.com"),
    };
    
    // user.username move
    let name = user.username;
    
    // println!("{}", user.username);  // ❌ ERROR: partially moved
    println!("{}", user.email);        // ✅ OK
}

Struct với References (cần Lifetime)

rust
// ❌ Không compile - thiếu lifetime
// struct User {
//     username: &str,
//     email: &str,
// }

// ✅ Với lifetime annotation
struct User<'a> {
    username: &'a str,
    email: &'a str,
}

fn main() {
    let name = String::from("alice");
    let email = String::from("alice@example.com");
    
    let user = User {
        username: &name,
        email: &email,
    };
    
    println!("{}", user.username);
}

Bảng Tóm tắt

ConceptSyntaxUse Case
Named Structstruct Name { field: Type }Most common, clear field names
Tuple Structstruct Name(Type1, Type2)Newtype pattern, positional data
Unit Structstruct Name;Marker types, zero-cost abstraction
Enumenum Name { Variant1, Variant2(T) }Sum types, multiple variants
Option<T>Some(T) / NoneNullable values, safe null handling
Result<T, E>Ok(T) / Err(E)Error handling
matchmatch expr { pattern => arm }Exhaustive pattern matching
if letif let Pattern = expr { }Single pattern, concise

Bài tập: Fix Compiler Errors

💪 Bài 1: Handle Option properly
rust
fn main() {
    let numbers = vec![1, 2, 3];
    let first = numbers.first();  // Returns Option<&i32>
    
    let doubled = first * 2;  // ❌ ERROR
    println!("{}", doubled);
}

Đáp án:

rust
fn main() {
    let numbers = vec![1, 2, 3];
    let first = numbers.first();
    
    // Cách 1: match
    let doubled = match first {
        Some(n) => n * 2,
        None => 0,
    };
    
    // Cách 2: map + unwrap_or
    let doubled = first.map(|n| n * 2).unwrap_or(0);
    
    // Cách 3: if let
    if let Some(n) = first {
        println!("{}", n * 2);
    }
}
💪 Bài 2: Make match exhaustive
rust
enum Status {
    Active,
    Inactive,
    Pending,
    Deleted,
}

fn describe(status: Status) -> &'static str {
    match status {
        Status::Active => "active",
        Status::Inactive => "inactive",
    }
}

Đáp án:

rust
fn describe(status: Status) -> &'static str {
    match status {
        Status::Active => "active",
        Status::Inactive => "inactive",
        Status::Pending => "pending",
        Status::Deleted => "deleted",
    }
}

// Hoặc với catch-all
fn describe_short(status: Status) -> &'static str {
    match status {
        Status::Active => "active",
        _ => "other",
    }
}