Bài học theo mô-đun (2/2)
Con trỏ thông minh: Box và Rc
con trỏ thông minh là cấu trúc dữ liệu hoạt động giống như một con trỏ nhưng có siêu dữ liệu và các khả năng bổ sung (chẳng hạn như tính tham chiếu hoặc quản lý bộ nhớ tự động).
Trong Rust, các tham chiếu thông thường (như &) chỉ mượn dữ liệu. Mặt khác, con trỏ thông minh thường sở hữu dữ liệu mà chúng trỏ tới. Các ví dụ phổ biến bao gồm String và Vec<T>, nhưng thư viện chuẩn cung cấp các thư viện chuyên biệt khác.
Box<T> để phân bổ vùng heap
Con trỏ thông minh đơn giản nhất là Box<T>, lưu trữ dữ liệu trên heap thay vì ngăn xếp. Chỉ còn lại con trỏ tới dữ liệu được cấp phát heap trên ngăn xếp.
Nó chủ yếu được sử dụng trong các trường hợp sau:
- Khi bạn có một loại có kích thước không thể xác định được tại thời điểm biên dịch (như loại đệ quy) và bạn muốn sử dụng giá trị của loại đó trong ngữ cảnh yêu cầu kích thước chính xác.
- Khi bạn muốn chuyển quyền sở hữu một lượng lớn dữ liệu mà không cần sao chép.
Ví dụ cơ bản:
fn main() {
let b = Box::new(5); // allocates the value 5 on the heap
println!("b = {}", *b); // dereferences with * to read the value
}Danh sách các loại đệ quy và nhược điểm
Kiểu đệ quy là kiểu có thể chứa giá trị của chính nó như một phần trong định nghĩa của nó. Rust phải biết kích thước chính xác của một loại tại thời điểm biên dịch. Vì độ sâu của kiểu đệ quy có thể là vô hạn nên trình biên dịch sẽ tạo ra lỗi trừ khi bạn sử dụng Hộp (có kích thước cố định vì nó là con trỏ):
enum List {
Cons(i32, Box<List>),
Nil,
}Rc<T>: Đếm tham chiếu
Có những trường hợp một giá trị có thể có nhiều chủ sở hữu. Con trỏ thông minh Rc<T> (Đếm tham chiếu) theo dõi số lượng tham chiếu đến một giá trị để xác định xem giá trị đó có còn được sử dụng hay không. Nếu số lượng giảm xuống 0, tài nguyên sẽ được giải phóng an toàn.
[!LƯU Ý]
Rc<T>chỉ phù hợp với các trường hợp đơn luồng. Đối với các kịch bản đa luồng (đồng thời), bạn phải sử dụngArc<T>(Đã tính tham chiếu nguyên tử).
use std::rc::Rc;
fn main() {
let a = Rc::new(5);
let b = Rc::clone(&a); // increments the reference count to 2
println!("References: {}", Rc::strong_count(&a));
}Hãy tự mình thử
Bài tập 1: Phân bổ heap với Box
Phân bổ một giá trị nguyên 42 trên heap bằng cách sử dụng Box::new và gán nó cho một biến có tên val. Tiếp theo, in giá trị được lưu trữ trong Hộp bằng cách hủy tham chiếu rõ ràng bằng toán tử *.
Hiển thị gợi ý
Sử dụng `let val = Box::new(42);` để lưu trữ giá trị trong heap và hủy đăng ký nó với `*val` bên trong `println!`.
Giải pháp khả dụng sau 3 lần thử
Bài tập 2: Cấu trúc dữ liệu đệ quy (Danh sách nhược điểm)
Xác định một enum đệ quy có tên List chứa hai biến thể: Cons bao bọc một bộ dữ liệu (i32, Box<List>) và Nil đại diện cho phần cuối của danh sách. Trong hàm main, khởi tạo một danh sách liên kết chứa phần tử 1 theo sau là Nil.
Hiển thị gợi ý
Enum được khai báo là `enum List { Cons(i32, Box<List>), Nil }`. Khởi tạo danh sách bằng `List::Cons(1, Box::new(List::Nil))`.
Giải pháp khả dụng sau 3 lần thử
Bài 3: Chia sẻ quyền sở hữu với Rc
Sử dụng std::rc::Rc để tạo số nguyên dùng chung chứa giá trị 100 trong biến a. Sao chép tham chiếu vào biến b bằng cách sử dụng Rc::clone. Cuối cùng, in số lượng tham chiếu đang hoạt động bằng hàm Rc::strong_count.
Hiển thị gợi ý
Tạo giá trị bằng `Rc::new(100)`. Sao chép bằng `Rc::clone(&a)`và hiển thị số lượng chủ sở hữu bằng`Rc::strong_count(&a)`.
Giải pháp khả dụng sau 3 lần thử