ฉันได้อ่านคำว่า "fat pointer" ในหลาย ๆ บริบทแล้ว แต่ฉันไม่แน่ใจว่ามันหมายถึงอะไรและเมื่อใดที่ใช้ใน Rust ตัวชี้ดูเหมือนจะใหญ่กว่าตัวชี้ปกติถึงสองเท่า แต่ฉันไม่เข้าใจว่าทำไม ดูเหมือนว่าจะมีบางอย่างเกี่ยวข้องกับวัตถุลักษณะ
ฉันได้อ่านคำว่า "fat pointer" ในหลาย ๆ บริบทแล้ว แต่ฉันไม่แน่ใจว่ามันหมายถึงอะไรและเมื่อใดที่ใช้ใน Rust ตัวชี้ดูเหมือนจะใหญ่กว่าตัวชี้ปกติถึงสองเท่า แต่ฉันไม่เข้าใจว่าทำไม ดูเหมือนว่าจะมีบางอย่างเกี่ยวข้องกับวัตถุลักษณะ
คำตอบ:
คำว่า "ตัวชี้ไขมัน" ใช้เพื่ออ้างถึงการอ้างอิงและตัวชี้ดิบไปยังประเภทขนาดแบบไดนามิก (DST) - ชิ้นส่วนหรือวัตถุลักษณะ ตัวชี้ไขมันประกอบด้วยตัวชี้พร้อมข้อมูลบางอย่างที่ทำให้ DST "สมบูรณ์" (เช่นความยาว)
ประเภทที่ใช้บ่อยที่สุดใน Rust ไม่ใช่ DST แต่มีขนาดคงที่ซึ่งทราบเวลาคอมไพล์ ประเภทนี้ใช้ลักษณะ แม้แต่ประเภทที่จัดการฮีปบัฟเฟอร์ที่มีขนาดไดนามิก (เช่น) ก็เป็นได้เนื่องจากคอมไพเลอร์รู้จำนวนไบต์ที่แน่นอนที่อินสแตนซ์จะใช้ในสแต็ก ปัจจุบันมี DST สี่ประเภทที่แตกต่างกันใน RustSized
Vec<T>
Sized
Vec<T>
[T]
และstr
)ประเภท[T]
(สำหรับรายการใด ๆT
) มีขนาดแบบไดนามิก (ดังนั้นประเภท "string slice" พิเศษstr
) นั่นเป็นเหตุผลที่คุณมักจะเห็นเป็น&[T]
หรือ&mut [T]
กล่าวคืออยู่เบื้องหลังข้อมูลอ้างอิง การอ้างอิงนี้เรียกว่า "ตัวชี้ไขมัน" ตรวจสอบ:
dbg!(size_of::<&u32>());
dbg!(size_of::<&[u32; 2]>());
dbg!(size_of::<&[u32]>());
สิ่งนี้พิมพ์ออกมา (ด้วยการล้างข้อมูลบางส่วน):
size_of::<&u32>() = 8
size_of::<&[u32; 2]>() = 8
size_of::<&[u32]>() = 16
ดังนั้นเราจะเห็นว่ามีการอ้างอิงถึงชนิดปกติเช่นu32
เป็น 8 [u32; 2]
ไบต์มีขนาดใหญ่ที่สุดเท่าที่มีการอ้างอิงไปยังอาร์เรย์ ทั้งสองประเภทไม่ใช่ DST แต่เช่นเดียว[u32]
กับ DST การอ้างอิงถึงมีขนาดใหญ่กว่าสองเท่า ในกรณีของชิ้นส่วนข้อมูลเพิ่มเติมที่ "เสร็จสมบูรณ์" DST เป็นเพียงความยาว ดังนั้นใคร ๆ ก็บอกได้ว่าการเป็นตัวแทน&[u32]
คืออะไรแบบนี้:
struct SliceRef {
ptr: *const u32,
len: usize,
}
dyn Trait
)เมื่อใช้ลักษณะเป็นวัตถุลักษณะ (เช่นประเภทลบออกส่งแบบไดนามิก) วัตถุลักษณะเหล่านี้คือ DST ตัวอย่าง:
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>());
สิ่งนี้พิมพ์ออกมา (ด้วยการล้างข้อมูลบางส่วน):
size_of::<&Cat>() = 8
size_of::<&dyn Animal>() = 16
อีกครั้ง&Cat
มีขนาดใหญ่เพียง 8 ไบต์เนื่องจากCat
เป็นประเภทปกติ แต่dyn Animal
เป็นวัตถุลักษณะจึงมีขนาดแบบไดนามิก ด้วยเหตุ&dyn Animal
นี้จึงมีขนาดใหญ่ 16 ไบต์
ในกรณีของ trait object ข้อมูลเพิ่มเติมที่ทำให้ DST สมบูรณ์คือตัวชี้ไปยัง vtable (vptr) ฉันไม่สามารถอธิบายแนวคิดของ vtables และ vptrs ได้อย่างสมบูรณ์ที่นี่ แต่ใช้เพื่อเรียกใช้วิธีการที่ถูกต้องในบริบทการจัดส่งเสมือนนี้ vtable เป็นส่วนข้อมูลคงที่ซึ่งโดยพื้นฐานแล้วจะมีตัวชี้ฟังก์ชันสำหรับแต่ละวิธีเท่านั้น ด้วยเหตุนี้การอ้างอิงถึงวัตถุลักษณะโดยทั่วไปจะแสดงเป็น:
struct TraitObjectRef {
data_ptr: *const (),
vptr: *const (),
}
(ซึ่งแตกต่างจาก C ++ ที่ vptr สำหรับคลาสนามธรรมจะถูกเก็บไว้ภายในออบเจ็กต์ทั้งสองวิธีมีข้อดีและข้อเสีย)
เป็นไปได้จริงที่จะสร้าง DST ของคุณเองโดยมีโครงสร้างที่ฟิลด์สุดท้ายเป็น DST สิ่งนี้ค่อนข้างหายาก std::path::Path
ตัวอย่างหนึ่งที่โดดเด่นคือ
การอ้างอิงหรือตัวชี้ไปยัง DST ที่กำหนดเองยังเป็นตัวชี้ไขมัน ข้อมูลเพิ่มเติมขึ้นอยู่กับชนิดของ DST ภายในโครงสร้าง
ในRFC 1861ที่extern type
คุณสมบัติได้รับการแนะนำ ประเภท Extern ก็เป็น DST เช่นกัน แต่คำแนะนำไม่ใช่ตัวชี้ไขมัน หรือมากกว่านั้นตามที่ RFC วางไว้:
ใน Rust ตัวชี้ไปยัง DST จะมีข้อมูลเมตาเกี่ยวกับวัตถุที่ชี้ไป สำหรับสตริงและชิ้นส่วนนี่คือความยาวของบัฟเฟอร์สำหรับ trait object นี่คือ vtable ของอ็อบเจ็กต์ ประเภท extern
()
เมตาดาต้าเป็นเพียง ซึ่งหมายความว่าตัวชี้ไปยังชนิดภายนอกมีขนาดเท่ากับ ausize
(กล่าวคือไม่ใช่ "ตัวชี้ไขมัน")
แต่ถ้าคุณไม่ได้โต้ตอบกับอินเทอร์เฟซ C คุณอาจไม่ต้องจัดการกับประเภทภายนอกเหล่านี้
ด้านบนเราได้เห็นขนาดสำหรับการอ้างอิงที่ไม่เปลี่ยนรูป ตัวชี้ไขมันทำงานเหมือนกันสำหรับการอ้างอิงที่ไม่แน่นอนตัวชี้ดิบที่ไม่เปลี่ยนรูปและตัวชี้ดิบที่ไม่แน่นอน:
size_of::<&[u32]>() = 16
size_of::<&mut [u32]>() = 16
size_of::<*const [u32]>() = 16
size_of::<*mut [u32]>() = 16