# 🧩 Traits và generics để trừu tượng hóa

By Everwise Intern

## Vì sao trait là xương sống của service ngân hàng

Bài trước bạn đã gắn lifetime vào domain model tiền tệ để `Transfer<'a>` mượn `Account` mà không phải clone. Nhưng khi nghiệp vụ lớn dần (nhiều loại phí, nhiều loại giao dịch, nhiều kênh nhập liệu), câu hỏi thật sự là: làm sao thêm loại mới mà không sửa hàng loạt `match` và không phá invariant tiền tệ? Trait và generics là câu trả lời, và bạn sẽ dùng lại chúng xuyên suốt capstone.

Sau bài này bạn cần:
- Viết được trait `Validatable` và `Pricing` cho domain tiền tệ.
- Dùng generics với trait bounds thay cho tư duy kế thừa kiểu Java/C#.
- Phân biệt static dispatch (`impl Trait`, generic) và dynamic dispatch (`dyn Trait`), biết chọn cái nào.
- Nhận diện 4 lỗi thiết kế thường gặp khi nghiệp vụ mở rộng.

## Trait: hợp đồng hành vi, không phải cây kế thừa

Trong Rust không có `extends`. Một trait mô tả **một khả năng**: "kiểu này validate được", "kiểu này tính được giá". Type nào muốn tham gia thì `impl` tường minh. Điều này quan trọng với fintech vì bạn kiểm soát chính xác type nào được đi vào luồng tiền.

### Validation: mỗi khả năng một trait nhỏ

```rust
pub trait Validatable {
    type Error;
    fn validate(&self) -> Result<(), Self::Error>;
}

impl Validatable for Transfer {
    type Error = ValidationError;

    fn validate(&self) -> Result<(), Self::Error> {
        if self.amount.amount_minor <= 0 {
            return Err(ValidationError::NonPositiveAmount);
        }
        if self.to_account.is_empty() {
            return Err(ValidationError::MissingBeneficiary);
        }
        Ok(())
    }
}
```

`type Error` là associated type: mỗi impl chọn đúng **một** kiểu lỗi, và compiler ép bạn nhất quán. Đây là chỗ dễ sai nhất của người mới: đừng gộp validation, pricing, persistence vào một trait `AccountService`. Trait càng to thì impl càng dễ phải viết `unimplemented!()`, tức là panic trong production.

> **Bài tập 1 (đưa vào capstone):** viết `impl Validatable for Withdrawal` với ba luật: amount dương, amount không vượt balance, currency khớp.

**Xem gợi ý**

```rust
impl Validatable for Withdrawal {
    type Error = ValidationError;

    fn validate(&self) -> Result<(), Self::Error> {
        if self.amount.amount_minor <= 0 {
            return Err(ValidationError::NonPositiveAmount);
        }
        if self.amount.amount_minor > self.account.balance.amount_minor {
            return Err(ValidationError::InsufficientFunds);
        }
        if self.amount.currency != self.account.currency {
            return Err(ValidationError::CurrencyMismatch);
        }
        Ok(())
    }
}
```

Trả `Result`, tuyệt đối không `assert!` hay `panic!`: một request sai không được làm chết cả service.

### Pricing: default method giữ invariant ở một chỗ

```rust
pub trait Pricing {
    fn unit_price(&self) -> Money;

    fn total(&self, qty: u32) -> Result<Money, PricingError> {
        self.unit_price()
            .checked_mul(qty)
            .ok_or(PricingError::Overflow)
    }
}
```

`total` là default method: mọi impl chỉ cần khai báo đơn giá, còn phép nhân có kiểm tra overflow nằm duy nhất một nơi. Nếu mỗi loại phí tự viết `amount * qty`, sớm muộn sẽ có một impl quên `checked_mul`.

### Generics với trait bounds thay cho kế thừa

```rust
pub fn quote_line<T>(item: &T, qty: u32) -> Result<Money, QuoteError>
where
    T: Validatable<Error = ValidationError> + Pricing,
{
    item.validate().map_err(QuoteError::Validation)?;
    item.total(qty).map_err(QuoteError::Pricing)
}
```

Đọc theo cách này: "tôi không cần biết `T` là gì, miễn nó validate được và tính được giá". Đó chính là composition thay cho kế thừa. Rust biên dịch generic bằng **monomorphization**, sinh code riêng cho từng kiểu cụ thể, nên không có chi phí gọi hàm ảo.

### `dyn Trait` khi cần danh sách chiến lược

```rust
pub trait FeeRule {
    fn apply(&self, amount: Money) -> Money;
    fn name(&self) -> &'static str;
}

pub struct FeeSchedule {
    rules: Vec<Box<dyn FeeRule>>,
}
```

Khi tập luật phí được cấu hình từ database, bạn không biết trước kiểu nào, nên `dyn Trait` là lựa chọn đúng. Cái giá: dynamic dispatch qua vtable, không inline được.

> **Bài tập 2:** vì sao `fn run(rules: &[impl FeeRule])` không nhận được `Vec` chứa nhiều loại phí khác nhau?

**Xem gợi ý**

`impl Trait` ở vị trí tham số là generic, mỗi lần gọi chỉ ứng với **một** kiểu cụ thể. Slice nhiều kiểu bắt buộc phải là `&[Box<dyn FeeRule>]`. Đổi lại, generic cho phép inline và kiểm tra kiểu tại compile time, nên dùng generic cho hàm nội bộ, `dyn` cho nơi cần linh hoạt.

### Bốn lỗi thiết kế khi nghiệp vụ mở rộng

- **God trait:** một trait ôm validation, pricing, persistence. Impl nào cũng phải viết method rỗng hoặc `unimplemented!()`.
- **Method generic trong trait cần `dyn`:** trait mất object safety, không tạo được `Box<dyn ...>`.
- **Impl trait ngoại cho type ngoại:** Rust chặn bằng **orphan rule**. Giải pháp là newtype: `pub struct Vnd(Money);`.
- **Trừu tượng quá sớm:** mới có một impl đã dựng trait, rồi phải sửa trait ba lần khi thêm nghiệp vụ.

## Cạm bẫy khi trừu tượng hóa tiền tệ

- **Rule of three:** đợi đến impl thứ hai hoặc thứ ba, hoặc đến khi cần mock trong test, rồi hãy tạo trait.
- **Đặt bound ở biên, không rải khắp nơi.** Chỉ hàm thật sự cần `T: Pricing` mới khai báo bound; đừng thêm bound vào struct.
- **Đừng nhét `Clone` vào bound để né borrow checker.** Trong luồng tiền, clone im lặng thường là dấu hiệu bạn đang che một lỗi ownership.
- **Đặt tên trait theo khả năng**, không theo thực thể: `Pricing`, `Validatable`, `FeeRule`, chứ không phải `AccountTrait`.
- **Sealed trait** (`pub trait Pricing: private::Sealed`) khi invariant tài chính không được cho crate ngoài impl. Nếu không, ai đó có thể tạo phép tính phí sai và bạn không kiểm soát được.

## Chốt lại và bước tiếp theo

Ba điều cần mang theo: trait mô tả **một khả năng**, generics + bounds thay cho kế thừa, và `dyn Trait` chỉ dùng khi thật sự cần một collection nhiều kiểu. Kiểm tra nhanh: generic được biên dịch theo hướng _____, còn `dyn Trait` dùng _____ qua vtable. Muốn chặn crate ngoài impl trait của mình, dùng pattern _____. Không thể impl trait ngoại cho type ngoại vì _____.

Bài tiếp theo là checkpoint Module 1: bạn sẽ ghép ownership, borrowing, lifetimes, trait và generics vào một bài tính toán tiền hoàn chỉnh, không clone thừa và không panic.

### Thẻ ôn tập nhanh

Front: Trait trong Rust tương ứng khái niệm nào ở Java/C#?
Back: Interface, nhưng là hợp đồng hành vi tách rời, không có kế thừa implementation theo cây lớp.

Front: `impl Trait` ở vị trí tham số tương đương với gì?
Back: Một generic parameter có trait bound, tức static dispatch.

Front: Khi nào nên dùng `Box<dyn Trait>`?
Back: Khi cần field hoặc collection chứa nhiều kiểu khác nhau cùng implement một trait.

Front: Chi phí của `dyn Trait` là gì?
Back: Dynamic dispatch qua vtable: gọi hàm gián tiếp, không inline được.

Front: Default method trong trait dùng để làm gì?
Back: Chia sẻ implementation chung, ví dụ `total()` gọi `unit_price()` để invariant nằm một chỗ.

Front: Vì sao trait có method generic không dùng được làm trait object?
Back: Nó mất object safety, nên không tạo được `Box<dyn Trait>`.

Front: Associated type khác generic parameter ở điểm nào?
Back: Associated type: mỗi impl chọn đúng một kiểu; generic: một impl phục vụ nhiều kiểu.

Front: Orphan rule phát biểu thế nào?
Back: Chỉ được impl trait local cho type ngoại, hoặc trait ngoại cho type local.

Front: Newtype wrapper giải quyết vấn đề gì?
Back: Cho phép impl trait ngoại trên type ngoại và tạo ranh giới domain rõ ràng, ví dụ `struct Vnd(Money)`.

Front: Sealed trait là gì?
Back: Trait có supertrait private trong module riêng, chặn crate ngoài impl.

Front: Rule of three khi trừu tượng hóa là gì?
Back: Đợi có 2-3 impl, hoặc cần mock trong test, rồi mới tạo trait.

Front: God trait gây hại gì trong codebase ngân hàng?
Back: Buộc impl phải viết `unimplemented!()` (panic) và mọi thay đổi lan ra toàn bộ hệ thống.

Front: Tại sao không nên thêm `Clone` vào trait bound cho tiện?
Back: Nó che lỗi ownership và tạo bản sao dữ liệu tiền không cần thiết.

---

## Contents

Part of [🦀 Rust core cho fintech: nắm ownership để làm codebase tài chính](https://everwise.study/courses/rust-core-cho-fintech-nam-ownership-de-lam-codebase-tai-chinh.md)

- **🦀 Rust core cho fintech**
  - [🧠 Ownership và borrowing khi xử lý tiền](https://everwise.study/courses/rust-core-cho-fintech-nam-ownership-de-lam-codebase-tai-chinh/ownership-borrowing-xu-ly-tien.md)
  - [🔗 Lifetimes và domain model tài chính](https://everwise.study/courses/rust-core-cho-fintech-nam-ownership-de-lam-codebase-tai-chinh/lifetimes-domain-model-tai-chinh.md) (sign-in required)
  - [🧩 Traits và generics để trừu tượng hóa](https://everwise.study/courses/rust-core-cho-fintech-nam-ownership-de-lam-codebase-tai-chinh/traits-generics-truu-tuong-hoa.md) — this lesson
  - [✅ Checkpoint: Rust core cho fintech](https://everwise.study/courses/rust-core-cho-fintech-nam-ownership-de-lam-codebase-tai-chinh/checkpoint-rust-core-fintech.md)
- **🛡️ Error handling và concurrency an toàn**
  - [🚨 Result thay cho panic trong luồng tiền](https://everwise.study/courses/rust-core-cho-fintech-nam-ownership-de-lam-codebase-tai-chinh/result-khong-panic-luong-tien.md)
  - [⚡ Async/await, Tokio và chia sẻ state an toàn](https://everwise.study/courses/rust-core-cho-fintech-nam-ownership-de-lam-codebase-tai-chinh/async-tokio-chia-se-state-an-toan.md)
  - [✅ Checkpoint: Error handling và concurrency](https://everwise.study/courses/rust-core-cho-fintech-nam-ownership-de-lam-codebase-tai-chinh/checkpoint-error-handling-concurrency.md)
- **🏦 Capstone: service ngân hàng bằng Rust**
  - [🧱 Dựng API với axum và cấu trúc project](https://everwise.study/courses/rust-core-cho-fintech-nam-ownership-de-lam-codebase-tai-chinh/dung-api-axum-cau-truc-project.md)
  - [💸 Xử lý giao dịch: validation và tính nhất quán](https://everwise.study/courses/rust-core-cho-fintech-nam-ownership-de-lam-codebase-tai-chinh/xu-ly-giao-dich-validation-nhat-quan.md) (premium content)
  - [🗄️ Kết nối database, test và quan sát lỗi](https://everwise.study/courses/rust-core-cho-fintech-nam-ownership-de-lam-codebase-tai-chinh/ket-noi-database-test-quan-sat-loi.md)
  - [✅ Checkpoint: Capstone service ngân hàng](https://everwise.study/courses/rust-core-cho-fintech-nam-ownership-de-lam-codebase-tai-chinh/checkpoint-capstone-service-ngan-hang.md)
- [Course cheatsheet](https://everwise.study/courses/rust-core-cho-fintech-nam-ownership-de-lam-codebase-tai-chinh/cheatsheet.md) (premium content)
