# 🔗 Lifetimes và domain model tài chính

By Everwise Intern

> Sign in to view the full content of this lesson.

## Nối tiếp bài ownership: khi reference phải bước ra khỏi một hàm

Bài trước bạn đã nắm luật borrow: một `&mut` tại một thời điểm, và reference không bao giờ được sống lâu hơn owner. Bài đó mọi thứ nằm gọn trong một hàm. Thực tế trong service ngân hàng thì khác: dữ liệu đi qua nhiều hàm, nhiều struct, nhiều tầng. Bài này trả lời câu hỏi tiếp theo: khi nào compiler bắt bạn viết lifetime, và khi nào câu trả lời đúng lại là "đừng dùng reference nữa, dùng owned type".

Sau 30 phút này bạn cần:

- Đọc được lỗi lifetime và biết compiler đang chỉ vào đâu.
- Nhận ra 3 tình huống bắt buộc phải viết lifetime annotation.
- Thiết kế `Account`, `Transaction`, `Money` bằng owned type, đúng kiểu codebase tài chính.
- Biết khi nào struct có lifetime là lựa chọn tốt (report, view) và khi nào là bẫy.
- Tránh ba cạm bẫy tôi gặp nhiều nhất khi review code banking: lạm dụng `'static`, self-referential struct, và thêm lifetime để "chữa" lỗi compile.

## Lifetime là gì, và compiler đang hỏi bạn điều gì

Lifetime không phải tuổi thọ của giá trị. Nó là _____. Khi bạn viết `&Account`, compiler cần biết reference đó hợp lệ tới đâu để chứng minh không có chỗ nào dùng nó sau khi `Account` bị drop. `'a` chỉ là cái nhãn cho vùng đó.

Điểm quan trọng: bạn không "gán" lifetime cho biến. Bạn khai báo quan hệ giữa các reference, rồi compiler tự kiểm tra.

Phần lớn thời gian bạn không viết lifetime vì **elision rules** đã lo:

```rust
fn balance_of(account: &Account) -> &Money {
    &account.balance
}
// compiler hiểu thành:
// fn balance_of<'a>(account: &'a Account) -> &'a Money
```

Quy tắc cần nhớ: mỗi input reference có lifetime riêng; nếu function chỉ có một input reference thì output nhận _____; nếu có `&self` thì output nhận lifetime của `self`.

## Khi nào bắt buộc phải viết lifetime annotation

Ba tình huống, và chỉ ba:

1. **Struct hoặc enum chứa reference.** Kiểu dữ liệu đó bắt buộc phải khai báo _____.
2. **Function có nhiều input reference và trả về reference.** Compiler không đoán được output mượn từ tham số nào.
3. **Method trả về reference không xuất phát từ `&self`.** Hiếm, nhưng có.

Trường hợp 2 là chỗ hay gặp nhất:

```rust
// error[E0106]: missing lifetime specifier
fn older_account(a: &Account, b: &Account) -> &Account { ... }

// đúng: nói rõ output sống bằng vùng giao của cả hai
fn older_account<'a>(a: &'a Account, b: &'a Account) -> &'a Account {
    if a.opened_at <= b.opened_at { a } else { b }
}
```

Lưu ý cái sai phổ biến: nếu output chỉ đến từ **một** tham số, chỉ tham số đó cần `'a`, tham số còn lại để lifetime riêng. Viết `'a` cho tất cả là tự bó hẹp quyền dùng hàm.

> **Bài tập 1 (capstone):** bạn viết hàm tìm account theo id, có thêm tham số `note: &str` để log. Vì sao signature dưới đây không compile, và sửa thế nào?

```rust
fn find_account(accounts: &[Account], note: &str) -> Option<&Account>
```

**Xem đáp án**

Có hai input reference nên elision không áp dụng, compiler báo E0106. Chỉ `accounts` là nguồn của kết quả, nên chỉ nó cần lifetime:

```rust
fn find_account<'a>(accounts: &'a [Account], note: &str) -> Option<&'a Account> {
    // ... dùng note để log
}
```

## Domain model tài chính: mặc định là owned type

Đây là bài học đắt nhất tôi rút ra sau nhiều lần refactor: **entity nghiệp vụ thì dùng owned type, không dùng reference**.

```rust
pub struct AccountId(pub u64);

#[derive(Clone, Copy, PartialEq, Eq)]
pub enum Currency { Vnd, Usd }

#[derive(Clone, Copy)]
pub struct Money {
    pub amount_minor: i64, // VND: minor = đồng, USD: minor = cent
    pub currency: Currency,
}

pub struct Account {
    pub id: AccountId,
    pub owner_name: String,
    pub balance: Money,
    pub opened_at: u64,
}

pub struct Transaction {
    pub id: u64,
    pub from: AccountId,
    pub to: AccountId,
    pub amount: Money,
}
```

Chú ý `Transaction` trỏ tới account bằng _____, không bằng `&Account`. Lý do rất thực tế: transaction được validate, ghi DB, đẩy qua channel, đọc lại vài giờ sau. Reference không sống được qua những ranh giới đó, và borrow checker sẽ chặn bạn ngay khi thử. Trong banking, quan hệ giữa entity là **ID**, không phải reference.

Lỗi kinh điển khi mới học là cố làm ngược lại:

```rust
struct Account<'a> { owner_name: &'a str }

fn make_account<'a>() -> Account<'a> {
    let name = String::from("Nguyen Van A");
    Account { owner_name: &name }
}
// error[E0515]: cannot return value referencing local variable `name`
```

Đây chính là _____. Sửa đúng không phải thêm lifetime, mà là đổi sang `String`.

> **Bài tập 2 (capstone):** chuyển struct sau sang owned type và nêu một trường hợp vẫn nên giữ bản có lifetime.

```rust
struct Transfer<'a> { from: &'a Account, amount: Money }
```

**Xem đáp án**

```rust
struct Transfer { from: AccountId, amount: Money }
```

Bản có lifetime vẫn hữu ích khi `Transfer` chỉ là view tạm trong một hàm nội bộ, ví dụ bước validate đọc account đã load sẵn, không lưu lại, không gửi đi đâu. Ngay khi nó cần đi qua async, DB hay channel, hãy quay về owned.

## Khi nào struct có lifetime thực sự thắng

Có một chỗ lifetime là lựa chọn đúng: báo cáo read-only trong cùng một scope, nơi bạn không muốn clone hàng nghìn transaction chỉ để đọc.

```rust
pub struct AccountStatement<'a> {
    pub account: &'a Account,
    pub rows: Vec<&'a Transaction>,
    pub total_debit: Money,
}

pub fn build_statement<'a>(
    account: &'a Account,
    txns: &'a [Transaction],
) -> AccountStatement<'a> {
    // duyệt và tham chiếu, không clone
    todo!()
}
```

```mermaid
flowchart TD
    Start["Struct moi"] --> Q1{"Di qua async, DB hay channel?"}
    Q1 -->|Co| Owned["Owned type<br/>String, AccountId, Money"]
    Q1 -->|Khong| Q2{"Chi doc tam trong mot scope?"}
    Q2 -->|Co| Borrowed["Struct co lifetime<br/>AccountStatement"]
    Q2 -->|Khong| Owned
```

Một cảnh báo ngắn: nếu future giữ `&Account` trỏ vào dữ liệu local, `tokio::spawn` sẽ đòi `'static` và bạn sẽ phải đổi sang owned hoặc `Arc`. Chi tiết để bài async/await lo.

## Những lỗi lifetime tôi gặp nhiều nhất khi review code banking

- **Hiểu sai `'static`.** Nó không có nghĩa là "giá trị sống mãi trong bộ nhớ". Nó là _____. `&'static str` từ string literal là ví dụ.
- **Thêm lifetime để tắt lỗi.** Nếu bạn phải viết ba lifetime cho một struct nghiệp vụ, gần như chắc chắn thiết kế sai. Dừng lại, đổi sang owned.
- **Self-referential struct.** Struct vừa sở hữu `String` vừa giữ `&str` trỏ vào chính nó là bất khả thi an toàn. Dùng ID, index, hoặc `Arc`.
- **Nghe lời compiler "consider using 'static".** Trong service production, đây thường là lời khuyên dẫn tới chỗ khác cũng hỏng. Hãy đổi sang owned type.
- **Rule of thumb:** entity đi qua async/DB/channel thì owned; view đọc tạm trong một scope thì lifetime.

## Mang gì sang bài traits và generics

Ba điều cần nhớ: lifetime là ràng buộc về phạm vi hợp lệ của reference, không phải tuổi thọ giá trị; chỉ ba tình huống bắt buộc annotate; và domain model tài chính mặc định dùng owned type, reference chỉ dành cho view tạm.

Bài tiếp theo chúng ta sẽ trừu tượng hóa trên chính domain model này: dùng traits và generics để viết một `LedgerRepository` hay `FeePolicy` mà không phải sửa `Account` mỗi lần thêm loại phí mới.

---

## 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) — this lesson
  - [🧩 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)
  - [✅ 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)
