Danh sách bài viết

Bài 91: Enum Variant Với Data

Bài 91 của series Rust Cơ Bản — sức mạnh thực sự của enum Rust nằm ở chỗ mỗi variant có thể mang theo data riêng, theo hai dạng: tuple-like (Shape::Circle(f64), payload không tên truy cập theo thứ tự) và struct-like (Shape::Circle { radius: f64 }, payload có tên field như struct thường). Một enum cũng có thể mix nhiều dạng variant — kinh điển là Message trong Rust Book có cả unit, tuple, struct variant. Đây chính là tagged union / sum type mà Haskell, OCaml, Swift, TypeScript discriminated union đã có lâu, nhưng C/C++/Java thiếu vắng. Bài này phân tích cú pháp tuple-like vs struct-like, mixed variant, cách match destructure data ra biến local, memory layout (discriminant + max variant size), cách viết method trên enum chứa data, và các use case thực tế: HTTP Response body, JSON AST, UI event.

09/06/2026
12 phút đọc
1 lượt xem
1

Mục Tiêu Bài Học

Sau bài học, bạn sẽ:

  • Hiểu enum Rust là tagged union / sum type: mỗi variant có thể mang data riêng, mạnh hơn nhiều so với enum C chỉ là integer alias.
  • Viết được tuple-like variant (Circle(f64), Rectangle(f64, f64)) — payload không tên, truy cập theo thứ tự.
  • Viết được struct-like variant (Circle { radius: f64 }) — payload có tên field, đọc code tự document hơn.
  • Mix được unit + tuple + struct variant trong cùng enum (kinh điển là Message trong Rust Book).
  • Destructure từng variant trong match để bind data ra biến local — đây là cách duy nhất an toàn để lấy payload ra.
  • Hiểu memory layout enum: discriminant chọn variant + payload kích thước bằng variant lớn nhất; variant nhỏ vẫn pay cost cho variant to nhất.
  • Viết được impl block với method match self truy cập data trong từng arm (ví dụ Shape::area()).
  • Nhận diện được các use case nên dùng enum-with-data thay struct/object hierarchy: HTTP Response body, JSON AST node, UI event, state machine.
2

Variant Chứa Data — Tagged Union

Ở Bài 90, enum Direction chỉ là 4 tag không kèm data — gần giống enum C. Nhưng enum Rust có một thuộc tính rất quan trọng: mỗi variant có thể mang theo data riêng, và data của các variant không cần cùng kiểu. Đây chính là khái niệm tagged union (hay sum type, discriminated union) trong lý thuyết kiểu.

So sánh với C để thấy sự khác biệt:

  • C enum: chỉ là một int được đặt tên — enum Color { RED, GREEN, BLUE }; tương đương RED = 0, GREEN = 1, BLUE = 2. Không có cách nào để "RED đi kèm thêm intensity, GREEN đi kèm 2 số float".
  • C union: nhiều field chia chung một vùng bộ nhớ — không có cách an toàn biết hiện tại union đang chứa field nào, lập trình viên phải tự nhớ.
  • Rust enum: kết hợp cả hai — tag (discriminant) cho biết variant nào đang active + payload chứa data tương ứng. Compiler bắt buộc bạn xử lý đủ mọi variant trong match nên không thể đọc nhầm field.

Vì sao gọi là "sum type"? Vì số trạng thái khả dĩ của enum = tổng số trạng thái của từng variant. Ngược lại struct là "product type" — số trạng thái = tích số trạng thái của từng field. Sum type rất mạnh để model "hoặc cái này, hoặc cái kia" — kiểu logic OR có dữ liệu kèm theo.

Một ví dụ thực tế: hình học. Một Shape có thể là hình tròn (chỉ cần bán kính) hoặc hình chữ nhật (cần chiều rộng + chiều cao). Hai variant có shape data khác hẳn nhau, không thể nhét chung vào một struct mà không lãng phí hoặc lằng nhằng Option. Enum-with-data là công cụ thiết kế hoàn hảo cho tình huống này.

3

Tuple-Like Variant

Dạng đầu tiên: variant chứa data theo cú pháp tuple — danh sách kiểu trong cặp ngoặc tròn, không có tên field.

#[derive(Debug)]
enum Shape {
    Circle(f64),              // 1 field: bán kính
    Rectangle(f64, f64),      // 2 field: width, height
    Triangle(f64, f64, f64),  // 3 field: cạnh a, b, c
}

fn main() {
    // Tạo instance: gọi như function constructor
    let c = Shape::Circle(2.5);
    let r = Shape::Rectangle(3.0, 4.0);
    let t = Shape::Triangle(3.0, 4.0, 5.0);

    println!("c = {c:?}");   // Circle(2.5)
    println!("r = {r:?}");   // Rectangle(3.0, 4.0)
    println!("t = {t:?}");   // Triangle(3.0, 4.0, 5.0)
}

Vài điểm chú ý cú pháp:

  • Khai báo variant: VariantName(Type1, Type2, ...) — danh sách kiểu giống tuple struct.
  • Khởi tạo instance: Shape::Circle(2.5) — viết như gọi function. Thực tế Rust tạo ra một constructor function ngầm cùng tên với variant.
  • Không có tên field, truy cập data phải qua match hoặc if let để destructure ra biến — không có syntax c.0 giống tuple struct (sẽ thấy ở mục 6).

Tuple-like variant phù hợp khi:

  • Variant chỉ có 1-2 data, ý nghĩa của data rõ từ tên variant — như Circle(f64) ai cũng đoán đây là bán kính.
  • Cần cú pháp gọn, không muốn dài dòng tên field.
  • Tương thích pattern matching ngắn gọn ở phía consumer.

Nhưng khi variant có 3+ field hoặc semantics field dễ nhầm (ví dụ Rectangle(f64, f64) — đâu là width, đâu là height?), nên cân nhắc dạng struct-like ở mục tiếp theo.

4

Struct-Like Variant

Dạng thứ hai: variant chứa data theo cú pháp struct — field có tên rõ ràng, viết trong cặp ngoặc nhọn.

#[derive(Debug)]
enum Shape {
    Circle { radius: f64 },
    Rectangle { width: f64, height: f64 },
    Triangle { a: f64, b: f64, c: f64 },
}

fn main() {
    // Tạo instance: y hệt khởi tạo struct, có tên field
    let c = Shape::Circle { radius: 2.5 };
    let r = Shape::Rectangle { width: 3.0, height: 4.0 };
    let t = Shape::Triangle { a: 3.0, b: 4.0, c: 5.0 };

    println!("c = {c:?}");
    // Circle { radius: 2.5 }

    println!("r = {r:?}");
    // Rectangle { width: 3.0, height: 4.0 }

    println!("t = {t:?}");
    // Triangle { a: 3.0, b: 4.0, c: 5.0 }
}

Khác biệt chính so với tuple-like:

  • Khai báo: VariantName { field1: Type1, field2: Type2 } — block field giống struct thường.
  • Khởi tạo: Shape::Rectangle { width: 3.0, height: 4.0 } — phải nêu tên field, không thể nhầm thứ tự.
  • Debug print thân thiện hơn: hiện cả tên field, dễ đọc khi log/debug.

Struct-like variant phù hợp khi:

  • Variant có nhiều field (3 trở lên) hoặc field cùng kiểu dễ nhầm thứ tự (như Rectangle(f64, f64) ai biết đâu là width).
  • Muốn code self-documenting — nhìn vào lệnh tạo instance là biết ngay từng số dùng làm gì.
  • Có khả năng thêm field mới trong tương lai mà không phá vỡ thứ tự tham số ở callsite.

Hai dạng tuple-like và struct-like tương đương về biểu đạt — chọn dạng nào phụ thuộc gu code và độ rõ ràng. Nhiều thư viện Rust nổi tiếng dùng struct-like cho variant phức tạp và tuple-like cho variant đơn giản 1 field.

5

Mixed Variant — Enum Message Kinh Điển

Quy tắc không bắt buộc tất cả variant của một enum phải cùng dạng. Một enum có thể đồng thời chứa unit variant (không data), tuple variant, và struct variant. Ví dụ kinh điển từ The Rust Programming Language Book:

#[derive(Debug)]
enum Message {
    Quit,                          // unit variant - không data
    Move(i32, i32),                // tuple variant - 2 i32
    Write(String),                 // tuple variant - 1 String
    ChangeColor { r: u8, g: u8, b: u8 },  // struct variant
}

fn main() {
    let messages = [
        Message::Quit,
        Message::Move(10, 20),
        Message::Write(String::from("Hello, blogcode.vn!")),
        Message::ChangeColor { r: 255, g: 128, b: 0 },
    ];

    for m in &messages {
        println!("{m:?}");
    }
    // Quit
    // Move(10, 20)
    // Write("Hello, blogcode.vn!")
    // ChangeColor { r: 255, g: 128, b: 0 }
}

Đây chính là sức mạnh của enum Rust: một loại nhưng có nhiều dạng khác hẳn nhau về data shape. So với hệ thống OOP truyền thống (Java/C#), bạn sẽ phải tạo một abstract class Message và 4 subclass — code lằng nhằng, không có compile-time check cho việc xử lý đủ mọi loại message.

Nếu viết bằng Rust với struct, bạn sẽ phải:

  • Tạo 4 struct riêng QuitMsg, MoveMsg, WriteMsg, ChangeColorMsg.
  • Tạo một trait Message chung và Box<dyn Message> để có polymorphism — kéo theo dynamic dispatch.
  • Không có cách bắt compiler kiểm tra "đã xử lý đủ mọi loại message" ở consumer code.

Với enum, tất cả gói gọn trong 6 dòng và compiler ép bạn xử lý đủ mọi variant. Đây là lý do enum-with-data được dùng rộng rãi trong Rust ecosystem cho mọi tình huống sum type.

6

Destructure Trong match

Đã chứa data trong variant rồi thì làm sao đọc data ra? Câu trả lời là pattern matching qua match — destructure variant để bind data thành biến local trong từng arm.

#[derive(Debug)]
enum Message {
    Quit,
    Move(i32, i32),
    Write(String),
    ChangeColor { r: u8, g: u8, b: u8 },
}

fn process(msg: Message) {
    match msg {
        Message::Quit => {
            println!("Thoát chương trình");
        }
        Message::Move(x, y) => {
            // x, y là biến local bind từ tuple variant
            println!("Di chuyển tới ({x}, {y})");
        }
        Message::Write(text) => {
            // text bind từ String trong Write
            println!("Ghi: {text}");
        }
        Message::ChangeColor { r, g, b } => {
            // destructure struct variant - field-init shorthand
            println!("Đổi màu sang RGB({r}, {g}, {b})");
        }
    }
}

fn main() {
    process(Message::Quit);
    process(Message::Move(10, 20));
    process(Message::Write(String::from("hello")));
    process(Message::ChangeColor { r: 255, g: 128, b: 0 });
}

Cú pháp destructure tương ứng với cú pháp khởi tạo, chỉ khác là thay value bằng tên biến:

  • Unit variant: Message::Quit => ... — không destructure gì, chỉ match tag.
  • Tuple variant: Message::Move(x, y) => ... — bind 2 i32 thành 2 biến local x, y theo thứ tự.
  • Struct variant: Message::ChangeColor { r, g, b } => ... — bind theo tên field (field-init shorthand giống struct destructure ở Bài 84).

Vài mẹo hữu ích:

  • Không cần dùng hết field: Message::Move(x, _) => ..._ bỏ qua field không cần.
  • Rename biến khi destructure struct variant: Message::ChangeColor { r: red, g: green, b: blue } => ....
  • Compiler bắt buộc bạn cover đủ mọi variant (exhaustiveness check) — quên một variant là compile error, không phải warning. Đây là điểm an toàn rất quý.
  • Nếu thêm variant mới vào enum, mọi match đang tồn tại sẽ compile-fail cho tới khi xử lý variant mới — refactor cực kỳ an toàn.
7

Memory Layout — Discriminant + Max Variant Size

Hiểu memory layout của enum quan trọng khi optimize hiệu năng và FFI. Quy tắc cơ bản:

Size của enum = size của discriminant (tag) + size của variant lớn nhất (payload).

Trong đó discriminant là một số nguyên nhỏ để compiler biết hiện tại enum đang chứa variant nào. Payload dùng chung một vùng bộ nhớ cho mọi variant, kích thước phải đủ chứa variant to nhất.

use std::mem::size_of;

enum Small {
    A,           // 0 byte payload
    B,           // 0 byte payload
}

enum WithBig {
    Tag,                       // 0 byte payload
    Pair(u32, u32),            // 8 byte payload
    Huge([u8; 1024]),          // 1024 byte payload
}

fn main() {
    println!("size Small  = {}", size_of::<Small>());
    // 1 - chỉ cần 1 byte discriminant, không payload

    println!("size WithBig = {}", size_of::<WithBig>());
    // 1028 - discriminant + 1024 byte (variant Huge) + padding
    // CẢ variant Tag (không data) cũng pay 1028 byte!
}

Hệ quả thực hành rất quan trọng:

  • Một variant to làm toàn bộ enum to. Nếu 99% trường hợp dùng variant nhỏ và 1% dùng variant to, vẫn pay cost 1028 byte cho mọi instance. Đây là lý do Result<T, BigError> đôi khi nên đổi thành Result<T, Box<BigError>> để giảm size — payload chỉ còn 1 pointer.
  • Niche optimization: Rust compiler thông minh — nếu một variant có "khoảng giá trị không dùng" (như pointer không bao giờ null), compiler có thể nhét discriminant vào đó để enum không to thêm. Đây là lý do Option<Box<T>> chỉ tốn đúng 8 byte (kích thước pointer) — None encode bằng null pointer.
  • Discriminant size: default Rust chọn nhỏ nhất đủ chứa số variant (1 byte cho <=256 variant). Có thể ép kiểu qua attribute #[repr(u8)], #[repr(u16)] cho mục đích FFI / serialization.
  • Layout không xác định: thứ tự discriminant và payload trong memory không cố định trong Rust mặc định — compiler tự sắp xếp tối ưu. Khi cần layout cụ thể cho FFI, dùng #[repr(C)] hoặc #[repr(C, u8)] để compiler dùng layout giống C.

Tóm: không cần micro-optimize enum size cho hầu hết code thông thường, nhưng cần để ý khi: (a) variant chênh lệch size lớn, (b) enum được tạo hàng triệu instance, (c) FFI sang ngôn ngữ khác cần layout cố định.

8

Method Trên Enum Với Data

Y hệt struct, enum có thể có method qua impl block. Method thường match self để xử lý từng variant — đây là cách tổ chức code rất tự nhiên cho sum type.

#[derive(Debug)]
enum Shape {
    Circle { radius: f64 },
    Rectangle { width: f64, height: f64 },
    Triangle { a: f64, b: f64, c: f64 },
}

impl Shape {
    fn area(&self) -> f64 {
        match self {
            Shape::Circle { radius } => {
                std::f64::consts::PI * radius * radius
            }
            Shape::Rectangle { width, height } => {
                width * height
            }
            Shape::Triangle { a, b, c } => {
                // Công thức Heron
                let s = (a + b + c) / 2.0;
                (s * (s - a) * (s - b) * (s - c)).sqrt()
            }
        }
    }

    fn name(&self) -> &'static str {
        match self {
            Shape::Circle { .. } => "hình tròn",
            Shape::Rectangle { .. } => "hình chữ nhật",
            Shape::Triangle { .. } => "hình tam giác",
        }
    }
}

fn main() {
    let shapes = [
        Shape::Circle { radius: 2.0 },
        Shape::Rectangle { width: 3.0, height: 4.0 },
        Shape::Triangle { a: 3.0, b: 4.0, c: 5.0 },
    ];

    for s in &shapes {
        println!("{} - diện tích = {:.2}", s.name(), s.area());
    }
    // hình tròn - diện tích = 12.57
    // hình chữ nhật - diện tích = 12.00
    // hình tam giác - diện tích = 6.00
}

Một số điểm cú pháp quan trọng:

  • &self trong method — match trên self sẽ destructure ra reference đến field, không phải move. Trong arm dùng radius, width... là &f64, Rust tự deref khi tính toán.
  • Shape::Circle { .. } trong arm name() — dấu .. bỏ qua tất cả field, chỉ match variant tag mà không cần bind data ra biến.
  • Có thể có nhiều impl Shape { ... } block khác nhau — method nằm trong block nào cũng được, compiler gom chung.

Pattern "method = match self trên enum" thay thế đẹp cho virtual method / dynamic dispatch trong OOP — code tập trung tại một chỗ thay vì phân tán trong nhiều subclass, dễ đọc hơn cho domain nhỏ và không có dynamic cost.

9

Use Case Thực Tế: Response, AST, Event

Enum-with-data là công cụ design nền tảng — dưới đây vài ví dụ thường gặp trong production.

HTTP Response body

Một HTTP response có thể trả về text (HTML, plain text), JSON object, hoặc binary (image, file). Mô hình bằng enum tự nhiên:

use serde_json::Value;

#[derive(Debug)]
enum ResponseBody {
    Empty,
    Text(String),
    Json(Value),
    Binary(Vec<u8>),
}

fn content_length(body: &ResponseBody) -> usize {
    match body {
        ResponseBody::Empty => 0,
        ResponseBody::Text(s) => s.len(),
        ResponseBody::Json(v) => v.to_string().len(),
        ResponseBody::Binary(bytes) => bytes.len(),
    }
}

fn content_type(body: &ResponseBody) -> &'static str {
    match body {
        ResponseBody::Empty => "",
        ResponseBody::Text(_) => "text/plain; charset=utf-8",
        ResponseBody::Json(_) => "application/json",
        ResponseBody::Binary(_) => "application/octet-stream",
    }
}

Không thể có response vừa là text vừa là binary cùng lúc — sum type biểu đạt đúng ý đồ.

JSON parser AST

Cây cú pháp trừu tượng (AST) của JSON value là ví dụ nguyên bản cho sum type — một JSON value là một trong 6 dạng:

use std::collections::HashMap;

#[derive(Debug)]
enum JsonValue {
    Null,
    Bool(bool),
    Number(f64),
    String(String),
    Array(Vec<JsonValue>),
    Object(HashMap<String, JsonValue>),
}

fn pretty(v: &JsonValue) -> String {
    match v {
        JsonValue::Null => "null".to_string(),
        JsonValue::Bool(b) => b.to_string(),
        JsonValue::Number(n) => n.to_string(),
        JsonValue::String(s) => format!("\"{s}\""),
        JsonValue::Array(arr) => {
            let items: Vec<String> = arr.iter().map(pretty).collect();
            format!("[{}]", items.join(","))
        }
        JsonValue::Object(map) => {
            let items: Vec<String> = map.iter()
                .map(|(k, v)| format!("\"{k}\":{}", pretty(v)))
                .collect();
            format!("{{{}}}", items.join(","))
        }
    }
}

Đây cũng là cách serde_json::Value được định nghĩa — một enum 6 variant, mỗi variant chứa data tương ứng. Cú pháp pattern matching giúp viết visitor/serializer cực kỳ gọn.

UI Event

Mọi UI framework đều phải biểu diễn event — click, keypress, scroll, resize... với data khác hẳn nhau:

#[derive(Debug)]
enum UiEvent {
    Click { x: i32, y: i32, button: u8 },
    KeyPress(char),
    Scroll { delta_x: f32, delta_y: f32 },
    Resize { width: u32, height: u32 },
    Close,
}

fn handle(event: UiEvent) {
    match event {
        UiEvent::Click { x, y, button } => {
            println!("Click ({x},{y}) button {button}");
        }
        UiEvent::KeyPress(c) => println!("Phím: {c}"),
        UiEvent::Scroll { delta_x, delta_y } => {
            println!("Cuộn ({delta_x}, {delta_y})");
        }
        UiEvent::Resize { width, height } => {
            println!("Resize {width}x{height}");
        }
        UiEvent::Close => println!("Đóng cửa sổ"),
    }
}

Khi thêm loại event mới (ví dụ DoubleClick), compiler sẽ điểm danh mọi nơi match chưa cover — không bao giờ quên xử lý event mới như trong các framework dùng string event name. Tương tự tư duy là state machine: enum ConnectionState với các variant Disconnected / Connecting { since: Instant } / Connected { session_id: String } / Failed { error: String }.

10

Tổng Kết

  • Enum Rust là tagged union / sum type — mỗi variant có thể mang data riêng, mạnh hơn nhiều enum C (chỉ là int) hoặc union C (không có tag an toàn).
  • Tuple-like variant: Circle(f64), Rectangle(f64, f64) — payload không tên, khởi tạo như gọi function Shape::Circle(2.5); phù hợp variant ít field và semantics rõ.
  • Struct-like variant: Circle { radius: f64 } — payload có tên field, khởi tạo như struct Shape::Circle { radius: 2.5 }; phù hợp variant nhiều field hoặc cần self-document.
  • Mixed variant trong cùng enum: Message::Quit (unit) + Message::Move(i32, i32) (tuple) + Message::ChangeColor { r, g, b } (struct) — hoàn toàn hợp lệ và phổ biến.
  • Destructure trong match: Message::Move(x, y) => ..., Message::Write(text) => ..., Message::ChangeColor { r, g, b } => ... — bind data ra biến local; compiler ép cover đủ mọi variant.
  • Memory layout: size enum = discriminant (1 byte mặc định nếu <=256 variant) + max(variant payload size); variant nhỏ vẫn pay cost variant to nhất. Niche optimization giúp Option<Box<T>> chỉ tốn 1 pointer.
  • Method trên enum qua impl: thường match self destructure trong từng arm; thay cho virtual method OOP, không có dynamic dispatch cost.
  • Use case: HTTP Response body, JSON AST (như serde_json::Value), UI event, state machine — bất cứ tình huống "hoặc cái này, hoặc cái kia, kèm data khác nhau".
11

Bài Tập Củng Cố

Tự trả lời, đáp án ở cuối:

  1. Thiết kế enum Vehicle với 3 variant: Car (struct variant, có plate: Stringseats: u8), Motorbike (tuple variant, có 1 String biển số), Bicycle (unit variant). Viết hàm describe(v: &Vehicle) -> String match từng variant in ra mô tả phù hợp.
  2. Cho enum Shape ở mục 8 (struct-like Circle/Rectangle/Triangle). Viết method perimeter(&self) -> f64 trả về chu vi: hình tròn 2 * PI * r, hình chữ nhật 2 * (w + h), hình tam giác a + b + c.
  3. Cho enum Event { Click(i32, i32), KeyPress(char), Quit }. Viết arm match cho Event::Click(x, y) chỉ in x mà bỏ qua y — sử dụng pattern gì?
  4. Một enum Payload có 2 variant: Tiny(u8)Big([u8; 4096]). Ước tính size_of::<Payload>() và giải thích vì sao tổng size lớn dù Tiny chỉ chứa 1 byte. Đề xuất cách giảm size nếu phần lớn instance là Tiny.
  5. Khi nào nên dùng tuple-like variant và khi nào nên dùng struct-like variant? Nêu 2 tiêu chí cụ thể cho mỗi trường hợp.
Đáp án
  1. enum Vehicle {
        Car { plate: String, seats: u8 },
        Motorbike(String),
        Bicycle,
    }
    
    fn describe(v: &Vehicle) -> String {
        match v {
            Vehicle::Car { plate, seats } =>
                format!("Ô tô biển {plate}, {seats} chỗ"),
            Vehicle::Motorbike(plate) =>
                format!("Xe máy biển {plate}"),
            Vehicle::Bicycle =>
                String::from("Xe đạp"),
        }
    }
  2. impl Shape {
        fn perimeter(&self) -> f64 {
            match self {
                Shape::Circle { radius } =>
                    2.0 * std::f64::consts::PI * radius,
                Shape::Rectangle { width, height } =>
                    2.0 * (width + height),
                Shape::Triangle { a, b, c } => a + b + c,
            }
        }
    }
  3. Dùng dấu gạch dưới _ để bỏ qua field không cần: Event::Click(x, _) => println!("x = {x}"). _ match bất cứ giá trị nào mà không bind tên — tránh được cảnh báo unused variable.
  4. size_of::<Payload>()4097 byte (4096 cho Big + 1 byte discriminant, có thể padding lên 4104). Tất cả instance Tiny cũng tốn 4097 byte dù chỉ chứa 1 byte data thực — vì payload phải đủ chứa variant lớn nhất. Cách giảm: dùng Box<[u8; 4096]> trong variant Big để payload chỉ còn 1 pointer (8 byte): enum Payload { Tiny(u8), Big(Box<[u8; 4096]>) } → size chỉ còn ~16 byte, phần lớn data sống trên heap khi cần.
  5. Tuple-like khi: (a) variant chỉ 1-2 field và ý nghĩa rõ từ tên variant (như Some(T), Circle(f64)); (b) ưu tiên cú pháp gọn ở callsite. Struct-like khi: (a) variant có 3+ field hoặc field cùng kiểu dễ nhầm thứ tự (như Rectangle { width, height }); (b) cần code tự document, dễ thêm field mới mà không phá ordering ở callsite.
12

Bài Tiếp Theo

Bài 92: Option<T> — Some / None Thay Cho Null — sang một enum quan trọng nhất trong stdlib Rust: enum Option<T> { Some(T), None }. Rust không có null — mọi giá trị "có thể vắng mặt" đều mô hình qua Option. Bài sau sẽ phân tích lý do, các method tiện dụng (unwrap, expect, unwrap_or, map, toán tử ?) và pattern if let Some(x) = opt.

Bài này khởi đầu cụm enum chứa data (Bài 91-97). Các bài tiếp theo trong Nhóm 13 sẽ giới thiệu các enum chuẩn quan trọng (Option, Result), method trên enum, derive macro, discriminant explicit, và recursive enum cần Box.