Thuật ngữ "con trỏ béo" được sử dụng để chỉ các tham chiếu và con trỏ thô đến các loại có kích thước động (DST) - các lát cắt hoặc các đối tượng đặc điểm. Một con trỏ béo chứa một con trỏ cộng với một số thông tin làm cho DST "hoàn chỉnh" (ví dụ: độ dài).
Hầu hết các loại được sử dụng phổ biến trong Rust không phải là DST, nhưng có kích thước cố định đã biết tại thời điểm biên dịch. Những loại thực hiện các Sized
đặc điểm . Ngay cả các kiểu quản lý bộ đệm heap có kích thước động (như Vec<T>
) giống Sized
như trình biên dịch biết số byte chính xác mà một Vec<T>
cá thể sẽ chiếm trên ngăn xếp. Hiện có bốn loại DST khác nhau trong Rust.
Slices ( [T]
và str
)
Kiểu [T]
(cho bất kỳ T
) có kích thước động (kiểu "lát chuỗi" đặc biệt cũng vậy str
). Đó là lý do tại sao bạn thường chỉ thấy nó là &[T]
hoặc &mut [T]
, tức là đằng sau một tham chiếu. Tham chiếu này được gọi là "con trỏ béo". Hãy kiểm tra:
dbg!(size_of::<&u32>());
dbg!(size_of::<&[u32; 2]>());
dbg!(size_of::<&[u32]>());
Bản in này (với một số lần dọn dẹp):
size_of::<&u32>() = 8
size_of::<&[u32; 2]>() = 8
size_of::<&[u32]>() = 16
Vì vậy, chúng ta thấy rằng một tham chiếu đến một kiểu bình thường như u32
là 8 byte lớn, cũng như một tham chiếu đến một mảng [u32; 2]
. Hai loại đó không phải là DST. Nhưng cũng như [u32]
DST, tham chiếu đến nó lớn gấp đôi. Trong trường hợp các lát, dữ liệu bổ sung "hoàn thành" DST chỉ đơn giản là độ dài. Vì vậy, người ta có thể nói đại diện của &[u32]
là một cái gì đó như thế này:
struct SliceRef {
ptr: *const u32,
len: usize,
}
Đối tượng đặc điểm ( dyn Trait
)
Khi sử dụng các đặc điểm làm đối tượng đặc điểm (tức là loại bị xóa, di chuyển động), các đối tượng đặc điểm này là DST. Thí dụ:
trait Animal {
fn speak(&self);
}
struct Cat;
impl Animal for Cat {
fn speak(&self) {
println!("meow");
}
}
dbg!(size_of::<&Cat>());
dbg!(size_of::<&dyn Animal>());
Bản in này (với một số lần dọn dẹp):
size_of::<&Cat>() = 8
size_of::<&dyn Animal>() = 16
Một lần nữa, &Cat
chỉ lớn 8 byte vì Cat
là kiểu bình thường. Nhưng dyn Animal
là một đối tượng đặc điểm và do đó có kích thước động. Như vậy, &dyn Animal
là 16 byte lớn.
Trong trường hợp các đối tượng đặc điểm, dữ liệu bổ sung để hoàn thành DST là một con trỏ tới vtable (vptr). Tôi không thể giải thích đầy đủ khái niệm vtables và vptrs ở đây, nhưng chúng được sử dụng để gọi việc triển khai phương thức chính xác trong ngữ cảnh điều phối ảo này. Vtable là một phần dữ liệu tĩnh về cơ bản chỉ chứa một con trỏ hàm cho mỗi phương thức. Với điều đó, một tham chiếu đến một đối tượng đặc điểm về cơ bản được biểu diễn dưới dạng:
struct TraitObjectRef {
data_ptr: *const (),
vptr: *const (),
}
(Điều này khác với C ++, trong đó vptr cho các lớp trừu tượng được lưu trữ bên trong đối tượng. Cả hai cách tiếp cận đều có ưu điểm và nhược điểm.)
DST tùy chỉnh
Thực sự có thể tạo DST của riêng bạn bằng cách có một cấu trúc trong đó trường cuối cùng là DST. Tuy nhiên, điều này là khá hiếm. Một ví dụ nổi bật là std::path::Path
.
Tham chiếu hoặc con trỏ đến DST tùy chỉnh cũng là một con trỏ béo. Dữ liệu bổ sung phụ thuộc vào loại DST bên trong cấu trúc.
Ngoại lệ: Các loại ngoại lệ
Trong RFC 1861 , extern type
tính năng này đã được giới thiệu. Các kiểu extern cũng là DST, nhưng con trỏ tới chúng không phải là con trỏ béo. Hay chính xác hơn, như RFC đặt nó:
Trong Rust, con trỏ đến DST mang siêu dữ liệu về đối tượng được trỏ tới. Đối với các chuỗi và các lát, đây là độ dài của bộ đệm, đối với các đối tượng đặc điểm, đây là vtable của đối tượng. Đối với các loại extern, siêu dữ liệu chỉ đơn giản ()
. Điều này có nghĩa là một con trỏ tới kiểu extern có cùng kích thước với a usize
(tức là nó không phải là "con trỏ béo").
Nhưng nếu bạn không tương tác với giao diện C, bạn có thể sẽ không bao giờ phải đối mặt với những kiểu ngoại lệ này.
Ở trên, chúng ta đã thấy các kích thước cho các tham chiếu bất biến. Con trỏ béo hoạt động giống nhau đối với các tham chiếu có thể thay đổi, con trỏ thô bất biến và con trỏ thô có thể thay đổi:
size_of::<&[u32]>() = 16
size_of::<&mut [u32]>() = 16
size_of::<*const [u32]>() = 16
size_of::<*mut [u32]>() = 16