Danh sách bài viết

Bài 94: Method Trên Enum

Bài 94 của series Rust Cơ Bản — cho enum hành vi. Trong Rust, enum không chỉ là tập hợp variant mà là type đầy đủ, có thể có method, associated function, trait implementation. Cú pháp impl MyEnum { fn ... } giống hệt struct; điểm khác cốt lõi là body method gần như luôn dùng match self để dispatch theo variant. Bài này cover sáu pattern chính: method với match self trả về variant khác (Direction::opposite), state machine với transition (TrafficLight::next), method bóc data từ variant (Shape::area), associated function làm factory (Status::from_code trả Option<Self>), trait impl phổ biến (Display, From), và method với &mut self để mutate state. Khép lại với use case web backend — HttpMethod với loạt method semantic (is_safe, is_idempotent, requires_body) — đặt nền cho series Rust RESTful API sau này.

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

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

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

  • Viết được impl block cho enum với cú pháp giống struct: impl MyEnum { fn ... }; biết enum là type đầy đủ, có method, associated function, trait implementation.
  • Nắm pattern cốt lõi match self trong body method: dispatch theo variant, mỗi nhánh là một hành vi riêng — đây là cách viết method enum phổ biến nhất trong Rust.
  • Cài được state machine bằng enum + method transition: variant biểu diễn trạng thái, method next(&self) trả về state kế tiếp; chuyển hành vi state machine thành type-safe transition.
  • Bóc data trong variant qua pattern binding trong match self: Shape::Circle(r) => ... để dùng r, Shape::Rectangle(w, h) => ... dùng cả hai field.
  • Viết được associated function cho enum (không có self) như factory / smart constructor: Status::from_code(c: u16) -> Option<Self> trả Some khi hợp lệ, None khi không.
  • Impl trait phổ biến cho enum: Display để format đẹp, From<T> để convert — biến enum thành interoperable type, hoạt động với println!, format!, .into().
  • Dùng &mut self trong method để modify enum in-place — hợp lý cho counter, state machine khi muốn tránh re-assign.
  • Áp dụng tổng hợp qua use case web backend: HttpMethod enum với is_safe(), is_idempotent(), requires_body() — preview cho series Rust RESTful API.
2

impl Block Cho Enum

Trong Rust, enum là type đầy đủ — không phải tập hợp hằng số như enum trong C. Nó có thể có method, associated function, impl trait — cú pháp giống y hệt struct:

enum Direction {
    North,
    South,
    East,
    West,
}

impl Direction {
    // Method - tham số đầu là self (hoặc &self, &mut self)
    fn name(&self) -> &'static str {
        match self {
            Direction::North => "Bắc",
            Direction::South => "Nam",
            Direction::East => "Đông",
            Direction::West => "Tây",
        }
    }

    // Associated function - KHÔNG có self, gọi qua Direction::default()
    fn default() -> Self {
        Direction::North
    }
}

fn main() {
    let d = Direction::East;
    println!("hướng = {}", d.name());     // hướng = Đông

    let d0 = Direction::default();
    println!("mặc định = {}", d0.name()); // mặc định = Bắc
}

Vài điểm quan trọng:

  • impl Direction { ... }: block triển khai gắn với type Direction. Tất cả fn bên trong là method (nếu có self) hoặc associated function (nếu không có self).
  • self trong enum: tham chiếu đến instance hiện tại của enum, giá trị là một variant cụ thể. Dùng match self để rẽ nhánh theo variant.
  • Self viết hoa: alias cho chính type đang impl — trong impl Direction, SelfDirection. Viết fn default() -> Self đỡ phải lặp lại tên type, refactor đổi tên enum không phải sửa nhiều chỗ.
  • Có thể có nhiều impl block cho cùng enum — Rust gộp khi compile. Hữu ích khi muốn tách inherent method và trait impl, hoặc tách theo conditional compile (#[cfg(...)]).

Khả năng đặt method trực tiếp trên enum là một trong các điểm Rust khác biệt rõ so với C/C++/Java enum truyền thống. Nó cho phép đóng gói toàn bộ behavior thuộc về enum đó vào cùng một chỗ — đọc impl Direction hiểu ngay "Direction làm được gì".

3

Method Với match self

Pattern phổ biến NHẤT khi viết method enum: body method là một biểu thức match self, trả về kết quả tương ứng với variant. Ví dụ method opposite trả về hướng ngược lại:

#[derive(Debug)]
enum Direction {
    North,
    South,
    East,
    West,
}

impl Direction {
    fn opposite(&self) -> Self {
        match self {
            Direction::North => Direction::South,
            Direction::South => Direction::North,
            Direction::East => Direction::West,
            Direction::West => Direction::East,
        }
    }

    fn turn_right(&self) -> Self {
        match self {
            Direction::North => Direction::East,
            Direction::East => Direction::South,
            Direction::South => Direction::West,
            Direction::West => Direction::North,
        }
    }
}

fn main() {
    let d = Direction::North;
    println!("opposite of {d:?} = {:?}", d.opposite());   // South
    println!("turn right    = {:?}", d.turn_right());     // East

    // Chain method được vì opposite() trả Self
    let d2 = Direction::East.opposite().turn_right();
    println!("d2 = {d2:?}");  // West.turn_right() = North
}

Mấy điểm cần chú ý ở pattern này:

  • &self (mượn immutable) là default cho method enum — vì hầu hết method enum chỉ đọc variant rồi trả về giá trị mới. Không lấy ownership (không phải self) để gọi xong vẫn dùng được biến.
  • Trả về Self rất phổ biến — method tạo enum mới cùng type, cho phép chain như .opposite().turn_right(). Pattern giống builder và immutable transformation.
  • Exhaustive match: compiler bắt buộc liệt kê đủ variant — thêm variant Direction::Up mới mà chưa thêm nhánh, compile sẽ fail. Đây là type-safe behavior đặc trưng Rust: thêm variant = compiler nhắc nhở mọi method cần cập nhật.
  • Có thể trả về type khác Self: fn name(&self) -> &'static str trả string, fn is_horizontal(&self) -> bool trả bool, fn to_angle(&self) -> f64 trả góc... — method enum không giới hạn return type.

Nhìn lại: phần lớn method enum chỉ là match self + nhánh trả về giá trị. Đây là idiom cốt lõi cần thuộc nằm lòng.

4

State Machine Pattern

Một trong các ứng dụng đẹp nhất của method trên enum là state machine — máy trạng thái hữu hạn. Enum biểu diễn tập trạng thái, method next() (hoặc transition(event)) trả về trạng thái kế tiếp. Type system buộc bạn xử lý mọi trạng thái.

Ví dụ kinh điển — đèn giao thông:

#[derive(Debug, Clone, Copy, PartialEq)]
enum TrafficLight {
    Red,
    Green,
    Yellow,
}

impl TrafficLight {
    // Trạng thái tiếp theo: Red -> Green -> Yellow -> Red
    fn next(&self) -> Self {
        match self {
            TrafficLight::Red => TrafficLight::Green,
            TrafficLight::Green => TrafficLight::Yellow,
            TrafficLight::Yellow => TrafficLight::Red,
        }
    }

    fn duration_seconds(&self) -> u32 {
        match self {
            TrafficLight::Red => 30,
            TrafficLight::Green => 25,
            TrafficLight::Yellow => 5,
        }
    }

    fn can_go(&self) -> bool {
        matches!(self, TrafficLight::Green)
    }
}

fn main() {
    let mut light = TrafficLight::Red;

    for _ in 0..6 {
        println!(
            "{:?} - {}s - đi: {}",
            light,
            light.duration_seconds(),
            light.can_go()
        );
        light = light.next();
    }
}

Mấy lợi ích cụ thể của cách viết state machine bằng enum:

  • Trạng thái hợp lệ là một tập hữu hạn — compiler ngăn ngừa tạo state ngoài tập đó. Khác hẳn dùng String hay i32: "REd" hay 99 đều compile được nhưng vô nghĩa.
  • Transition rõ ràngnext() là single source of truth của quy luật chuyển. Đổi flow → sửa duy nhất method này.
  • Thêm trạng thái → compiler nhắc — thêm TrafficLight::Flashing, mọi match self đều fail biên dịch cho đến khi xử lý nhánh mới.
  • State machine phức tạp hơn dùng transition(event: Event) -> Self nhận event làm input, hoặc trả Result<Self, Error> khi transition không hợp lệ.

Pattern này xuất hiện khắp nơi: TCP connection state (Listen/SynRcvd/Established/Closed), workflow đơn hàng (Pending/Paid/Shipped/Delivered), authentication flow (Anonymous/LoggedIn/Verified), parser state... Đến mức Rust còn có type-state pattern nâng cao hơn (encode state vào generic type) — nhưng bắt đầu thường dùng enum đơn giản như trên là đủ.

5

Method Với Data Variant

Khi enum có variant chứa data (Bài 91), method match self cần bind data ra biến cục bộ để dùng. Pattern binding ngay trong nhánh match làm việc này gọn gàng:

use std::f64::consts::PI;

#[derive(Debug)]
enum Shape {
    Circle(f64),                    // bán kính
    Rectangle(f64, f64),            // width, height
    Triangle { base: f64, height: f64 },
}

impl Shape {
    fn area(&self) -> f64 {
        match self {
            Shape::Circle(r) => PI * r * r,
            Shape::Rectangle(w, h) => w * h,
            Shape::Triangle { base, height } => 0.5 * base * height,
        }
    }

    fn perimeter(&self) -> f64 {
        match self {
            Shape::Circle(r) => 2.0 * PI * r,
            Shape::Rectangle(w, h) => 2.0 * (w + h),
            // Tam giác: cần đủ ba cạnh — đây giả định cân để demo
            Shape::Triangle { base, height } => {
                let side = (base * base / 4.0 + height * height).sqrt();
                base + 2.0 * side
            }
        }
    }

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

fn main() {
    let shapes = [
        Shape::Circle(3.0),
        Shape::Rectangle(4.0, 5.0),
        Shape::Triangle { base: 6.0, height: 4.0 },
    ];

    for s in &shapes {
        println!(
            "{} - diện tích = {:.2}, chu vi = {:.2}",
            s.name(),
            s.area(),
            s.perimeter()
        );
    }
}

Mấy điểm về cú pháp binding cần nhớ:

  • Tuple variant: Shape::Circle(r) bind r bằng tham chiếu đến field (vì self&Self). Trong nhánh dùng r như &f64 — phép toán tự auto-deref.
  • Struct variant: Shape::Triangle { base, height } bind theo tên field. Có thể dùng { base, .. } bỏ qua field không cần, hoặc { base: b, height: h } đổi tên biến.
  • Bỏ qua data: dùng _ khi không cần — Shape::Circle(_) => "hình tròn", hoặc Shape::Triangle { .. } cho struct variant. Hữu ích khi method chỉ quan tâm loại variant.
  • Tên biến bind trong pattern chỉ tồn tại trong nhánh đór trong nhánh Circle không leak sang nhánh khác. Compiler scope rất chặt.

Method với data variant là cách Rust thể hiện polymorphism trên enum — một shape.area() dispatch đến đúng công thức tùy variant, không cần inheritance hay virtual call. Compiler biết toàn bộ tập variant ở compile time nên có thể inline hoàn toàn.

6

Associated Function Cho Enum

Associated functionfn trong impl KHÔNG có self — gọi qua EnumName::func(...). Vai trò phổ biến nhất: làm factory / smart constructor, trả về Option<Self> hoặc Result<Self, Error> khi việc tạo có thể fail:

#[derive(Debug)]
enum Status {
    Ok,
    Created,
    NotFound,
    InternalError,
}

impl Status {
    // Factory - convert HTTP code thành Status nếu hợp lệ
    fn from_code(code: u16) -> Option<Self> {
        match code {
            200 => Some(Status::Ok),
            201 => Some(Status::Created),
            404 => Some(Status::NotFound),
            500 => Some(Status::InternalError),
            _ => None,
        }
    }

    // Method instance - lấy code ngược lại
    fn code(&self) -> u16 {
        match self {
            Status::Ok => 200,
            Status::Created => 201,
            Status::NotFound => 404,
            Status::InternalError => 500,
        }
    }

    // Associated function - constructor mặc định
    fn default() -> Self {
        Status::Ok
    }
}

fn main() {
    // Gọi associated function qua ::
    let s1 = Status::from_code(404);   // Some(NotFound)
    let s2 = Status::from_code(999);   // None - code không support

    println!("{:?}", s1);
    println!("{:?}", s2);

    if let Some(s) = Status::from_code(201) {
        println!("status = {:?}, code = {}", s, s.code());
    }

    let d = Status::default();
    println!("default = {:?}", d);
}

Mấy lưu ý phân biệt:

  • Cú pháp gọi: associated function gọi qua Type::name(args); method gọi qua instance.name(args). Status::from_code(200) là associated function; s.code() là method.
  • Return Option<Self> hoặc Result<Self, E> khi tạo có thể fail — đây là pattern fallible constructor chuẩn Rust, an toàn hơn nhiều so với constructor ném exception (Java/Python) hoặc trả null (C).
  • Smart constructor: associated function có thể chứa validation logic — chỉ tạo được instance khi data hợp lệ. Phần này kết hợp với module privacy (giấu new field trực tiếp, chỉ expose qua from_code) tạo invariant cứng.
  • Tên new là convention cho constructor "không thể fail"; try_new, from_xxx, parse là convention cho fallible constructor. Stdlib dùng đầy: String::new(), String::from(...), u32::from_str_radix(...).
7

Trait Implementation Cho Enum

Ngoài inherent method (impl MyEnum { ... }), enum có thể impl trait — biến nó thành type tương thích với ecosystem. Hai trait phổ biến nhất khi mới học: Display (format đẹp cho println!) và From<T> (convert sang Self).

use std::fmt;

#[derive(Debug)]
enum Direction {
    North,
    South,
    East,
    West,
}

// impl Display - cho phép println!("{}", d), format!("{}", d)
impl fmt::Display for Direction {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let s = match self {
            Direction::North => "Bắc",
            Direction::South => "Nam",
            Direction::East => "Đông",
            Direction::West => "Tây",
        };
        write!(f, "{}", s)
    }
}

#[derive(Debug)]
enum Status {
    Ok,
    NotFound,
    InternalError,
    Unknown(u16),
}

// impl From<u16> - cho phép Status::from(404) và (404u16).into()
impl From<u16> for Status {
    fn from(code: u16) -> Self {
        match code {
            200 => Status::Ok,
            404 => Status::NotFound,
            500 => Status::InternalError,
            other => Status::Unknown(other),
        }
    }
}

fn main() {
    let d = Direction::East;

    // Display dùng {}
    println!("hướng: {d}");           // hướng: Đông
    // Debug vẫn dùng {:?} riêng
    println!("debug:  {d:?}");        // debug:  East

    // From<u16>
    let s: Status = Status::from(404);
    println!("{s:?}");

    // Cú pháp .into() — đối ngẫu của From
    let s2: Status = 500u16.into();
    println!("{s2:?}");
}

Mấy điểm cốt lõi về trait impl cho enum:

  • Display vs Debug: Display ({}) là format cho người dùng cuối — phải tự viết, không có derive. Debug ({:?}) là format cho lập trình viên — auto-derive được qua #[derive(Debug)]. Cả hai có thể tồn tại song song.
  • From<T>: khi impl From<T> for Self, Rust tự động impl Into<Self> for T — dùng value.into() hoặc Self::from(value) đều được. Pattern này phổ biến cho conversion type-safe.
  • Orphan rule vẫn áp dụng: impl Display (trait stdlib) cho Direction (type của bạn) OK; impl Display cho Vec<i32> (cả hai stdlib) thì cấm. Phải dùng newtype như đã học ở Bài 86.
  • Các trait phổ biến khác cho enum: Default (giá trị mặc định), FromStr (parse từ string), Hash + Eq (dùng làm key HashMap), Serialize/Deserialize (với serde). Mỗi trait là một "tính năng" enum được hưởng.

Trait là chủ đề của một nhóm bài lớn sau (Group về Traits). Ở đây bạn chỉ cần biết: enum impl trait cùng cú pháp như struct, và Display + From là hai trait mà gần như mọi enum nghiêm túc đều cần.

8

Method Mutable Self

Đa số method enum dùng &self và trả về variant mới (immutable transformation). Nhưng đôi khi muốn modify enum in-place — dùng &mut self rồi gán *self = new_variant;. Pattern này hợp lý cho counter, state machine khi tránh re-assign ở caller:

#[derive(Debug)]
enum Counter {
    Zero,
    NonZero(u32),
}

impl Counter {
    fn new() -> Self {
        Counter::Zero
    }

    // &mut self - được phép thay đổi *self
    fn increment(&mut self) {
        *self = match self {
            Counter::Zero => Counter::NonZero(1),
            Counter::NonZero(n) => Counter::NonZero(*n + 1),
        };
    }

    fn reset(&mut self) {
        *self = Counter::Zero;
    }

    fn value(&self) -> u32 {
        match self {
            Counter::Zero => 0,
            Counter::NonZero(n) => *n,
        }
    }
}

fn main() {
    let mut c = Counter::new();
    println!("init    = {:?} (value = {})", c, c.value());

    c.increment();
    c.increment();
    c.increment();
    println!("after 3 = {:?} (value = {})", c, c.value());

    c.reset();
    println!("reset   = {:?} (value = {})", c, c.value());
}

Lưu ý quan trọng:

  • *self = ... để gán giá trị mới qua mutable reference. Phải dereference (*) trước khi assign — nếu viết self = ... chỉ rebind biến cục bộ self, không ảnh hưởng instance gốc.
  • Khi nào nên dùng &mut self: khi việc modify in-place tự nhiên hơn — counter tăng dần, state machine chuyển bước trong loop, builder pattern set field. Tránh dùng cho transformation thuần (cứ &self -> Self rõ ràng hơn).
  • Biến caller phải khai báo mut: let mut c = Counter::new(); — không có mut, gọi c.increment() sẽ fail compile vì borrow checker phát hiện cần mutable borrow.
  • Pattern &mut self trên enum vẫn ít hơn struct rất nhiều — vì enum thường nhỏ, immutable transformation rẻ và dễ reason. Đừng lạm dụng.
9

Use Case Web Backend — HttpMethod

Tổng hợp các pattern đã học vào một use case thực tế — HttpMethod enum trong web backend. Đây là enum kinh điển nhất khi xây framework HTTP, và là preview cho series Rust RESTful API:

use std::fmt;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum HttpMethod {
    Get,
    Head,
    Post,
    Put,
    Patch,
    Delete,
    Options,
    Trace,
}

impl HttpMethod {
    // Associated function - parse từ string
    fn from_str(s: &str) -> Option<Self> {
        match s.to_ascii_uppercase().as_str() {
            "GET"     => Some(HttpMethod::Get),
            "HEAD"    => Some(HttpMethod::Head),
            "POST"    => Some(HttpMethod::Post),
            "PUT"     => Some(HttpMethod::Put),
            "PATCH"   => Some(HttpMethod::Patch),
            "DELETE"  => Some(HttpMethod::Delete),
            "OPTIONS" => Some(HttpMethod::Options),
            "TRACE"   => Some(HttpMethod::Trace),
            _         => None,
        }
    }

    // Method trả &'static str - tên method
    fn as_str(&self) -> &'static str {
        match self {
            HttpMethod::Get     => "GET",
            HttpMethod::Head    => "HEAD",
            HttpMethod::Post    => "POST",
            HttpMethod::Put     => "PUT",
            HttpMethod::Patch   => "PATCH",
            HttpMethod::Delete  => "DELETE",
            HttpMethod::Options => "OPTIONS",
            HttpMethod::Trace   => "TRACE",
        }
    }

    // Semantic: method an toàn (không thay đổi server state)
    // Theo RFC 9110 - GET, HEAD, OPTIONS, TRACE
    fn is_safe(&self) -> bool {
        matches!(
            self,
            HttpMethod::Get | HttpMethod::Head | HttpMethod::Options | HttpMethod::Trace
        )
    }

    // Idempotent: gọi N lần cùng kết quả như gọi 1 lần
    // Theo RFC 9110 - GET, HEAD, PUT, DELETE, OPTIONS, TRACE
    fn is_idempotent(&self) -> bool {
        !matches!(self, HttpMethod::Post | HttpMethod::Patch)
    }

    // Có request body không
    fn requires_body(&self) -> bool {
        matches!(self, HttpMethod::Post | HttpMethod::Put | HttpMethod::Patch)
    }
}

impl fmt::Display for HttpMethod {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}", self.as_str())
    }
}

fn main() {
    let methods = [
        HttpMethod::Get,
        HttpMethod::Post,
        HttpMethod::Put,
        HttpMethod::Delete,
        HttpMethod::Patch,
    ];

    println!("{:<8} {:<8} {:<12} {:<10}", "METHOD", "SAFE", "IDEMPOTENT", "HAS BODY");
    println!("{}", "-".repeat(40));
    for m in &methods {
        println!(
            "{:<8} {:<8} {:<12} {:<10}",
            m,
            m.is_safe(),
            m.is_idempotent(),
            m.requires_body()
        );
    }

    // Parse từ request line
    if let Some(m) = HttpMethod::from_str("post") {
        println!("\nparsed: {m} (safe={}, idem={})", m.is_safe(), m.is_idempotent());
    }
}

Output minh hoạ:

METHOD   SAFE     IDEMPOTENT   HAS BODY
----------------------------------------
GET      true     true         false
POST     false    false        true
PUT      false    true         true
DELETE   false    true         false
PATCH    false    false        true

parsed: POST (safe=false, idem=false)

Vì sao enum này đáng giá trong web framework:

  • Type-safe routing: fn route(method: HttpMethod, path: &str) ép caller truyền đúng method hợp lệ — không có chuyện gõ nhầm "GETT" chạy được rồi 404 ngầm.
  • Semantic API: if m.is_safe() { ... } đọc tự nhiên như tiếng Anh, code chỉ implement quy tắc RFC 9110 ở một chỗ duy nhất — sửa logic safe chỉ động đến method is_safe.
  • Cache layer: cache HTTP chỉ áp dụng cho is_safe() && is_idempotent() — viết một dòng if request.method.is_safe() && request.method.is_idempotent() { try_cache(...) }.
  • Body parsing: if m.requires_body() { read_body() } — không tốn công đọc body cho GET, không bỏ sót body cho POST.

Trong series Rust RESTful API (link cuối bài), bạn sẽ thấy enum tương tự xuất hiện trong crate axum, actix-web, warp — và họ còn impl thêm FromStr, Serialize, Hash, support cho method! macro... Nhưng bản chất vẫn là enum + một bộ method semantic. Cái bạn vừa viết là phiên bản giản hoá nhưng cùng tư duy.

10

Tổng Kết

  • impl block cho enum giống struct: impl MyEnum { fn ... }. Enum là type đầy đủ, có method (self, &self, &mut self), associated function (không self), và impl trait.
  • Pattern cốt lõi match self: body method gần như luôn là match self { ... } để dispatch theo variant; compiler bắt buộc exhaustive — thêm variant mới sẽ báo lỗi mọi method chưa cập nhật.
  • State machine: enum biểu diễn trạng thái + method next()/transition(event) trả state mới. Type-safe finite state machine không cần framework — pattern dùng cho TCP, workflow đơn, auth flow, parser.
  • Method với data variant: pattern binding trong nhánh — Shape::Circle(r), Shape::Triangle { base, height }, dùng _ hoặc .. bỏ qua field không cần. Đây là cách Rust thể hiện polymorphism không qua inheritance.
  • Associated function: gọi qua EnumName::func(...), không có self; vai trò chính là factory / smart constructor — trả Option<Self> hoặc Result<Self, E> khi tạo có thể fail (Status::from_code).
  • Trait implementation: impl Display for Direction để format đẹp ({}); impl From<u16> for Status để convert, tự động có .into(); orphan rule áp dụng giống struct.
  • &mut self: dùng *self = new_variant để modify in-place — hợp cho counter, state machine; biến caller phải let mut; chỉ dùng khi mutation tự nhiên hơn transformation.
  • Use case web backend: HttpMethod enum với is_safe(), is_idempotent(), requires_body(), as_str(), from_str() + impl Display — pattern xuất hiện trong mọi web framework Rust nghiêm túc.
11

Bài Tập Củng Cố

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

  1. Định nghĩa enum Season { Spring, Summer, Autumn, Winter }. Viết method next(&self) -> Self trả mùa kế tiếp (Spring → Summer → Autumn → Winter → Spring) và method average_temp(&self) -> f64 trả nhiệt độ trung bình giả định cho từng mùa (Spring 22, Summer 32, Autumn 25, Winter 15).
  2. Cho enum Shape { Circle(f64), Square(f64), Rectangle(f64, f64) }. Viết method area(&self) -> f64is_round(&self) -> bool. Giải thích tại sao Rust không cần inheritance hay virtual call cho polymorphism này.
  3. Viết enum LogLevel { Debug, Info, Warn, Error } với associated function from_str(s: &str) -> Option<Self> parse từ "debug", "info"... (case-insensitive), và method severity(&self) -> u8 trả số 0-3. Sau đó impl Display in tên uppercase.
  4. Định nghĩa state machine enum OrderStatus { Pending, Paid, Shipped, Delivered, Cancelled }. Viết method can_cancel(&self) -> bool (chỉ Pending/Paid được hủy) và next(&self) -> Option<Self> trả state kế (Pending → Paid → Shipped → Delivered → None; Cancelled không có next).
  5. Mở rộng HttpMethod ở Bước 9: thêm method allows_caching(&self) -> bool trả true khi method vừa safe vừa idempotent. Giải thích vì sao chỉ cần một dòng self.is_safe() && self.is_idempotent() mà không cần match self lần nữa.
Đáp án
  1. impl Season { fn next(&self) -> Self { match self { Season::Spring => Season::Summer, Season::Summer => Season::Autumn, Season::Autumn => Season::Winter, Season::Winter => Season::Spring } } fn average_temp(&self) -> f64 { match self { Season::Spring => 22.0, Season::Summer => 32.0, Season::Autumn => 25.0, Season::Winter => 15.0 } } }. Cả hai method dùng cùng pattern match self — đây là idiom phổ biến nhất.
  2. impl Shape { fn area(&self) -> f64 { match self { Shape::Circle(r) => std::f64::consts::PI * r * r, Shape::Square(s) => s * s, Shape::Rectangle(w, h) => w * h } } fn is_round(&self) -> bool { matches!(self, Shape::Circle(_)) } }. Rust không cần inheritance vì tập variant đóng — compiler biết toàn bộ Shape ở compile time, không có "class con runtime" như OOP. Mỗi shape.area() được dispatch qua match mà compiler có thể inline; chi phí runtime ngang viết hàm fn area_circle(r).
  3. impl LogLevel { fn from_str(s: &str) -> Option<Self> { match s.to_ascii_lowercase().as_str() { "debug" => Some(LogLevel::Debug), "info" => Some(LogLevel::Info), "warn" => Some(LogLevel::Warn), "error" => Some(LogLevel::Error), _ => None } } fn severity(&self) -> u8 { match self { LogLevel::Debug => 0, LogLevel::Info => 1, LogLevel::Warn => 2, LogLevel::Error => 3 } } } impl fmt::Display for LogLevel { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let s = match self { LogLevel::Debug => "DEBUG", LogLevel::Info => "INFO", LogLevel::Warn => "WARN", LogLevel::Error => "ERROR" }; write!(f, "{}", s) } }.
  4. impl OrderStatus { fn can_cancel(&self) -> bool { matches!(self, OrderStatus::Pending | OrderStatus::Paid) } fn next(&self) -> Option<Self> { match self { OrderStatus::Pending => Some(OrderStatus::Paid), OrderStatus::Paid => Some(OrderStatus::Shipped), OrderStatus::Shipped => Some(OrderStatus::Delivered), OrderStatus::Delivered => None, OrderStatus::Cancelled => None } } }. Trả Option<Self> vì transition có thể không tồn tại (terminal state Delivered/Cancelled) — Rust ép caller xử lý None qua match hoặc if let, không thể lỡ.
  5. fn allows_caching(&self) -> bool { self.is_safe() && self.is_idempotent() }. Không cần match self lần nữa vì is_safe()is_idempotent() đã tự dispatch nội bộ — composition tự động đúng cho mọi variant hiện tại VÀ tương lai. Thêm variant mới chỉ phải cập nhật is_safe/is_idempotent; allows_caching tự đúng. Đây là sức mạnh của method nhỏ + composition: code giảm trùng lặp, sửa một chỗ.
12

Bài Tiếp Theo

Bài 95: #[derive(Debug, Clone, PartialEq)] Cho Enum/Struct — đến giờ bạn đã viết khá nhiều #[derive(Debug)] mà chưa hiểu rõ nó là gì. Bài tiếp giải thích cơ chế derive macro auto-impl trait phổ biến (Debug in qua {:?}, Clone deep copy, PartialEq cho phép ==), restriction (mọi field/variant phải impl trait đó), và khi nào nên derive vs viết tay.