Giao diện
Data Modeling - Structs & Enums ADT
Định nghĩa custom types — Kết hợp Ownership với Type System
Structs: Gom nhóm Related Data
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ạiOption<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
| Concept | Syntax | Use Case |
|---|---|---|
| Named Struct | struct Name { field: Type } | Most common, clear field names |
| Tuple Struct | struct Name(Type1, Type2) | Newtype pattern, positional data |
| Unit Struct | struct Name; | Marker types, zero-cost abstraction |
| Enum | enum Name { Variant1, Variant2(T) } | Sum types, multiple variants |
Option<T> | Some(T) / None | Nullable values, safe null handling |
Result<T, E> | Ok(T) / Err(E) | Error handling |
match | match expr { pattern => arm } | Exhaustive pattern matching |
if let | if 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",
}
}