dpway

The middle way!

[Rust][009] Destructors

Tags = [ Rust ]

rust-cheat-sheet

Rust không cung cấp khối finally như trong các ngôn ngữ khác để đảm bảo một đoạn mã luôn được thực thi trước khi thoát khỏi một hàm. Thay vào đó, Rust sử dụng destructor của một đối tượng (Drop trait) để thực thi mã cần thiết trước khi chương trình thoát.

Destructors


2. Cách Hoạt Động

2.1. Tại Sao Cần Destructor?

Khi một hàm có nhiều điểm thoát (return ở nhiều nơi), việc đảm bảo một đoạn mã được thực thi trước khi hàm kết thúc có thể trở nên phức tạp và dễ gây lỗi. Điều này đặc biệt quan trọng khi sử dụng toán tử ?, vì nó có thể trả về kết quả sớm mà không cần lệnh return rõ ràng.

2.2. Ví Dụ

Ví dụ đơn giản về Destructor

struct Cleanup;

impl Drop for Cleanup {
    fn drop(&mut self) {
        println!("Cleaning up!");
    }
}

fn main() {
    let _c = Cleanup;
    println!("Doing some work...");
} // "Cleaning up!" sẽ được in khi _c ra khỏi phạm vi

Giải thích:

  • Cleanup triển khai Drop, giúp chạy mã dọn dẹp khi đối tượng ra khỏi phạm vi.
  • Khi main() kết thúc, _c sẽ bị hủy, và Rust tự động gọi destructor của Cleanup.

Ví dụ với return sớm

fn baz() -> Result<(), ()> {
    // Một số đoạn mã
    Ok(())
}

fn bar() -> Result<(), ()> {
    struct Foo;
    
    impl Drop for Foo {
        fn drop(&mut self) {
            println!("exit");
        }
    }
    
    let _exit = Foo; // Đối tượng này sẽ bị hủy khi ra khỏi phạm vi
    
    baz()?; // Nếu `baz` trả về `Err`, `Foo` vẫn bị hủy
    Ok(())
}

Giải thích:

  • Foo là một struct cục bộ chỉ tồn tại trong bar().
  • impl Drop for Foo định nghĩa hành vi khi Foo bị hủy.
  • Khi _exit ra khỏi phạm vi (scope), destructor của Foo sẽ chạy, bất kể hàm kết thúc như thế nào.
  • baz()? trả về lỗi (Err), Foo vẫn bị hủy bỏ đúng cách.

3. Ưu Điểm

  • Đảm bảo thực thi mã dọn dẹp: Destructor sẽ chạy trong hầu hết các trường hợp, kể cả khi có panic hay return sớm.
  • Giảm lặp code: Không cần viết nhiều đoạn mã giống nhau để xử lý cleanup.

4. Nhược Điểm

  • Không hoàn toàn đảm bảo chạy destructor: Nếu có một vòng lặp vô hạn hoặc lỗi nghiêm trọng (abort), destructor có thể không được gọi.
  • Khó phát hiện lỗi tiềm ẩn: Việc cleanup xảy ra ngầm có thể gây khó khăn khi debug.

5. Lưu Ý Khi Sử Dụng

  • Biến finalizer cần được gán vào một biến để tránh bị hủy ngay lập tức.
  • Nếu sử dụng shared pointer như Rc<T>, finalizer có thể tồn tại lâu hơn dự kiến.
  • Trong trường hợp panic xảy ra trong destructor khi Rust đang xử lý một panic khác, chương trình sẽ bị abort ngay lập tức.

6. Tổng Kết

Destructor trong Rust là một cơ chế hữu ích để đảm bảo mã cleanup được chạy tự động khi một đối tượng ra khỏi phạm vi. Tuy nhiên, lập trình viên cần hiểu rõ cách hoạt động của nó để tránh các lỗi khó phát hiện và đảm bảo tài nguyên được giải phóng đúng cách.