Mục lục
Mục Tiêu Bài Học
Sau bài học, bạn sẽ:
- Viết được
implblock 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 selftrong 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ùngr,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ảSomekhi hợp lệ,Nonekhi 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ớiprintln!,format!,.into(). - Dùng
&mut selftrong 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:
HttpMethodenum vớiis_safe(),is_idempotent(),requires_body()— preview cho series Rust RESTful API.
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 typeDirection. Tất cảfnbên trong là method (nếu cóself) hoặc associated function (nếu không cóself).selftrong enum: tham chiếu đến instance hiện tại của enum, giá trị là một variant cụ thể. Dùngmatch selfđể rẽ nhánh theo variant.Selfviết hoa: alias cho chính type đang impl — trongimpl Direction,Self≡Direction. Viếtfn 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
implblock 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ì".
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ảiself) để gọi xong vẫn dùng được biến.- Trả về
Selfrấ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::Upmớ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 strtrả string,fn is_horizontal(&self) -> booltrả bool,fn to_angle(&self) -> f64trả 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.
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
Stringhayi32:"REd"hay99đều compile được nhưng vô nghĩa. - Transition rõ ràng —
next()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ọimatch 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) -> Selfnhậ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à đủ.
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)bindrbằng tham chiếu đến field (vìselflà&Self). Trong nhánh dùngrnhư&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ặcShape::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 đó —
rtrong 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.
Associated Function Cho Enum
Associated function là fn 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 quainstance.name(args).Status::from_code(200)là associated function;s.code()là method. - Return
Option<Self>hoặcResult<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
newfield trực tiếp, chỉ expose quafrom_code) tạo invariant cứng. - Tên
newlà convention cho constructor "không thể fail";try_new,from_xxx,parselà convention cho fallible constructor. Stdlib dùng đầy:String::new(),String::from(...),u32::from_str_radix(...).
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:
DisplayvsDebug: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 implFrom<T> for Self, Rust tự động implInto<Self> for T— dùngvalue.into()hoặcSelf::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) choDirection(type của bạn) OK; implDisplaychoVec<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.
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ếtself = ...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 -> Selfrõ ràng hơn). - Biến caller phải khai báo
mut:let mut c = Counter::new();— không cómut, gọic.increment()sẽ fail compile vì borrow checker phát hiện cần mutable borrow. - Pattern
&mut selftrê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.
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 methodis_safe. - Cache layer: cache HTTP chỉ áp dụng cho
is_safe() && is_idempotent()— viết một dòngif 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.
Tổng Kết
implblock cho enum giống struct:impl MyEnum { fn ... }. Enum là type đầy đủ, có method (self,&self,&mut self), associated function (khôngself), 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ặcResult<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ảilet mut; chỉ dùng khi mutation tự nhiên hơn transformation.- Use case web backend:
HttpMethodenum vớiis_safe(),is_idempotent(),requires_body(),as_str(),from_str()+ implDisplay— pattern xuất hiện trong mọi web framework Rust nghiêm túc.
Bài Tập Củng Cố
Tự trả lời, đáp án ở cuối:
- Định nghĩa
enum Season { Spring, Summer, Autumn, Winter }. Viết methodnext(&self) -> Selftrả mùa kế tiếp (Spring → Summer → Autumn → Winter → Spring) và methodaverage_temp(&self) -> f64trả nhiệt độ trung bình giả định cho từng mùa (Spring 22, Summer 32, Autumn 25, Winter 15). - Cho
enum Shape { Circle(f64), Square(f64), Rectangle(f64, f64) }. Viết methodarea(&self) -> f64vàis_round(&self) -> bool. Giải thích tại sao Rust không cần inheritance hay virtual call cho polymorphism này. - Viết enum
LogLevel { Debug, Info, Warn, Error }với associated functionfrom_str(s: &str) -> Option<Self>parse từ "debug", "info"... (case-insensitive), và methodseverity(&self) -> u8trả số 0-3. Sau đó implDisplayin tên uppercase. - Định nghĩa state machine
enum OrderStatus { Pending, Paid, Shipped, Delivered, Cancelled }. Viết methodcan_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). - Mở rộng
HttpMethodở Bước 9: thêm methodallows_caching(&self) -> booltrả true khi method vừa safe vừa idempotent. Giải thích vì sao chỉ cần một dòngself.is_safe() && self.is_idempotent()mà không cầnmatch selflần nữa.
Đáp án
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 patternmatch self— đây là idiom phổ biến nhất.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ỗishape.area()được dispatch quamatchmà compiler có thể inline; chi phí runtime ngang viết hàmfn area_circle(r).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) } }.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ýNonequamatchhoặcif let, không thể lỡ.fn allows_caching(&self) -> bool { self.is_safe() && self.is_idempotent() }. Không cầnmatch selflần nữa vìis_safe()và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ậtis_safe/is_idempotent;allows_cachingtự đúng. Đây là sức mạnh của method nhỏ + composition: code giảm trùng lặp, sửa một chỗ.
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.
