dpway

The middle way!

[Rust][008] Collections Are Smart Pointers

Tags = [ Rust ]

rust-cheat-sheet

Trong Rust, collections không chỉ đơn giản là các cấu trúc dữ liệu chứa nhiều phần tử, mà còn hoạt động giống như smart pointers nhờ vào trait Deref. Điều này giúp chúng cung cấp cả quyền sở hữu và quyền mượn dữ liệu một cách linh hoạt.

Collections Are Smart Pointers


2. Cách Hoạt Động

2.1. Quan hệ giữa Vec<T>&[T]

  • Vec<T> là một collection sở hữu dữ liệu.
  • &[T] là một dạng mượn (borrowed view) của Vec<T>.
  • Trait Deref được triển khai để cho phép chuyển đổi tự động từ &Vec<T> sang &[T].

Ví dụ triển khai trait Deref cho Vec<T>:

use std::ops::Deref;

struct Vec<T> {
    data: RawVec<T>,
}

impl<T> Deref for Vec<T> {
    type Target = [T];

    fn deref(&self) -> &[T] {
        &self.data
    }
}

Giải thích:

  • Deref là một trait cho phép một kiểu dữ liệu tùy chỉnh có thể hành xử giống như một tham chiếu.
  • Trong đoạn mã trên, Vec<T> có một trường dữ liệu data, chứa các phần tử.
  • Khi chúng ta triển khai Deref cho Vec<T>, chúng ta định nghĩa type Target = [T], nghĩa là khi dereferencing Vec<T>, nó sẽ trở thành &[T] (một mảng tham chiếu).
  • Trong phương thức deref, chúng ta trả về &self.data, giúp Vec<T> có thể được sử dụng giống như một slice [T], nghĩa là có thể gọi các phương thức của slice trên Vec<T> mà không cần truy cập trực tiếp đến data.

Điều này giúp các phương thức chỉ có trên &[T] có thể được gọi trực tiếp trên Vec<T> mà không cần dereferencing thủ công.

2.2. Quan hệ giữa String&str

  • String là một collection sở hữu dữ liệu dạng chuỗi.
  • &str là phiên bản mượn của String.
  • Tương tự như Vec<T>, String triển khai Deref<Target = str> để cho phép truy cập dễ dàng hơn.

2.3. Smart Pointers và Collections

Smart pointers và collections có một điểm chung: chúng đều giúp quản lý bộ nhớ một cách hiệu quả.

  • Smart pointer trỏ đến một đối tượng duy nhất và đảm bảo quyền sở hữu bộ nhớ của đối tượng đó.
  • Collection chứa nhiều đối tượng và cũng chịu trách nhiệm quản lý bộ nhớ của chúng.

Về bản chất, một collection sở hữu dữ liệu của nó, nghĩa là nếu bạn muốn truy cập các phần tử bên trong, bạn phải thông qua collection. Khi collection bị hủy, tất cả dữ liệu bên trong cũng bị xóa. Tuy nhiên, để tránh việc sao chép dữ liệu không cần thiết, Rust cho phép các collection cung cấp một "view mượn" của dữ liệu bên trong. Điều này giúp lập trình viên có thể sử dụng dữ liệu mà không cần phải sở hữu nó.

Hầu hết smart pointers như Box<T> hoặc Rc<T> triển khai Deref<Target=T>, có nghĩa là chúng cho phép bạn truy cập trực tiếp vào đối tượng mà chúng trỏ đến. Ngược lại, collections thường triển khai Deref trỏ đến một kiểu dữ liệu khác. Ví dụ:

  • Vec<T> dereference về [T].
  • String dereference về str.

Ngoài ra, các collections có thứ tự như Vec<T> cũng triển khai Index để hỗ trợ truy cập phần tử bằng cách sử dụng slicing (&vec[1..3]). Đây là một cách khác để tạo ra một view mượn của dữ liệu trong collection mà không cần sao chép.

3. Ưu Điểm

  • Tái sử dụng API: Các phương thức có thể được triển khai trên dạng mượn, giúp tái sử dụng và tiết kiệm bộ nhớ.
  • Linh hoạt trong quản lý bộ nhớ: Lập trình viên có thể lựa chọn giữa sở hữu và mượn dữ liệu tùy theo nhu cầu.

4. Nhược Điểm

  • Giới hạn trong lập trình tổng quát: Các phương thức và trait chỉ có thể truy cập thông qua dereferencing, gây khó khăn khi làm việc với generic.
  • Khả năng nhầm lẫn khi sử dụng: Vì Rust tự động dereference, có thể khó nhận biết khi nào đang làm việc với phiên bản sở hữu hay mượn.

5. Tổng Kết

Sử dụng collections như smart pointers là một mẫu thiết kế mạnh mẽ trong Rust, giúp quản lý bộ nhớ hiệu quả và cho phép API linh hoạt hơn. Tuy nhiên, cần hiểu rõ cơ chế Deref để tránh những nhầm lẫn khi triển khai trong các dự án lớn.