我已经在多个上下文中阅读过“脂肪指针”一词,但是我不确定它的确切含义以及何时在Rust中使用它。该指针似乎是普通指针的两倍,但我不明白为什么。它也似乎与特征对象有关。
答案 0 :(得分:68)
“胖指针”一词用于指代动态大小类型(DST)(切片或特征对象)的引用和原始指针。胖指针包含一个指针以及使DST“完成”的某些信息(例如长度)。
Rust中最常用的类型是 not DST,但是在编译时已知有固定的大小。这些类型实现the Sized
trait。甚至管理动态大小的堆缓冲区的类型(例如Vec<T>
)也都是Sized
,因为编译器知道Vec<T>
实例将在堆栈上占用的确切字节数。目前,Rust中有四种不同的DST。
[T]
和str
)类型[T]
(对于任何T
)是动态调整大小的(特殊的“字符串切片”类型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
)的引用以及对数组[u32; 2]
的引用都是8个字节。这两种类型不是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对象,完成DST的其他数据是指向vtable(vptr)的指针。在这里,我无法完全解释vtables和vptr的概念。用于在此虚拟调度上下文中调用正确的方法实现。 vtable是静态数据,基本上每个方法只包含一个函数指针。这样,对特征对象的引用基本上表示为:
struct TraitObjectRef {
data_ptr: *const (),
vptr: *const (),
}
(这与C ++不同,在C ++中,抽象类的vptr存储在对象内。这两种方法都有优点和缺点。)
实际上,可以通过具有最后一个字段为DST的结构来创建自己的DST。但是,这种情况很少见。一个著名的例子是std::path::Path
。
对自定义DST的引用或指针也是胖指针。附加数据取决于结构内部DST的类型。
在RFC 1861中,引入了extern type
功能。 Extern类型也是DST,但指向它们的指针是 not 胖指针。或更确切地说,如RFC所述:
在Rust中,指向DST的指针携带有关所指向对象的元数据。对于字符串和切片,这是缓冲区的长度,对于特征对象,这是对象的vtable。对于外部类型,元数据只是
()
。这意味着指向extern类型的指针的大小与usize
相同(即,它不是“胖指针”)。
但是,如果您不与C接口进行交互,则可能永远不必处理这些外部类型。
上面,我们已经看到了不可变引用的大小。胖指针对可变引用,不变的原始指针和可变的原始指针的作用相同:
size_of::<&[u32]>() = 16
size_of::<&mut [u32]>() = 16
size_of::<*const [u32]>() = 16
size_of::<*mut [u32]>() = 16