什么是“胖指针"?在Rust中? [英] What is a "fat pointer" in Rust?

查看:1407
本文介绍了什么是“胖指针"?在Rust中?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我已经在多个上下文中读过脂肪指针"一词,但是我不确定它的确切含义以及在Rust中使用它的时间.该指针似乎是普通指针的两倍,但我不明白为什么.它似乎也与特征对象有关.

I've read the term "fat pointer" in several contexts already, but I'm not sure what exactly it means and when it is used in Rust. The pointer seems to be twice as large as a normal pointer, but I don't understand why. It also seems to have something to do with trait objects.

推荐答案

术语胖指针"用于指代动态大小类型(DST)的引用和原始指针–切片或特征对象.胖指针包含一个指针以及一些使DST完成"(例如长度)的信息.

The term "fat pointer" is used to refer to references and raw pointers to dynamically sized types (DSTs) – slices or trait objects. A fat pointer contains a pointer plus some information that makes the DST "complete" (e.g. the length).

Rust中最常用的类型是 not DST,但是在编译时已知有固定的大小.这些类型实现 Sized特征.甚至管理动态大小的堆缓冲区的类型(如Vec<T>)也为Sized,因为编译器知道Vec<T>实例将在堆栈上占用的确切字节数.目前,Rust中有四种DST.

Most commonly used types in Rust are not DSTs, but have a fixed size known at compile time. These types implement the Sized trait. Even types that manage a heap buffer of dynamic size (like Vec<T>) are Sized as the compiler knows the exact number of bytes a Vec<T> instance will take up on the stack. There are currently four different kinds of DSTs in Rust.


[T]类型(对于任何T)都是动态调整大小的(特殊的字符串切片"类型str也是如此).这就是为什么您通常只将其视为&[T]&mut [T]的原因,即在引用后面.该参考是所谓的胖指针".让我们检查一下:

The type [T] (for any T) is dynamically sized (so is the special "string slice" type str). That's why you usually only see it as &[T] or &mut [T], i.e. behind a reference. This reference is a so-called "fat pointer". Let's check:

dbg!(size_of::<&u32>());
dbg!(size_of::<&[u32; 2]>());
dbg!(size_of::<&[u32]>());

此打印(进行一些清理):

This prints (with some cleanup):

size_of::<&u32>()      = 8
size_of::<&[u32; 2]>() = 8
size_of::<&[u32]>()    = 16

因此,我们看到对普通类型(如u32)的引用和对数组[u32; 2]的引用一样,它们的大小为8个字节.这两种类型不是DST.但是由于[u32]是DST,因此对其的引用是其两倍. 对于切片,完成" DST的其他数据就是长度.因此,可以说&[u32]的表示是这样的:

So we see that a reference to a normal type like u32 is 8 bytes large, as is a reference to an array [u32; 2]. Those two types are not DSTs. But as [u32] is a DST, the reference to it is twice as large. In the case of slices, the additional data that "completes" the DST is simply the length. So one could say the representation of &[u32] is something like this:

struct SliceRef { 
    ptr: *const u32, 
    len: usize,
}


当使用特征作为特征对象(即类型已擦除,动态分派)时,这些特征对象就是DST.示例:

When using traits as trait objects (i.e. type erased, dynamically dispatched), these trait objects are DSTs. Example:

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>());

此打印(进行一些清理):

This prints (with some cleanup):

size_of::<&Cat>()        = 8
size_of::<&dyn Animal>() = 16

同样,&Cat只有8个字节大,因为Cat是普通类型.但是dyn Animal是特征对象,因此可以动态调整大小.因此,&dyn Animal为16字节大.

Again, &Cat is only 8 bytes large because Cat is a normal type. But dyn Animal is a trait object and therefore dynamically sized. As such, &dyn Animal is 16 bytes large.

就特征对象而言,完成DST的其他数据是指向vtable(vptr)的指针.在此,我无法完全解释vtable和vptr的概念,但它们是用于在此虚拟调度上下文中调用正确的方法实现. vtable是静态数据,基本上每个方法只包含一个函数指针.这样,对特征对象的引用基本上表示为:

In the case of trait objects, the additional data that completes the DST is a pointer to the vtable (the vptr). I cannot fully explain the concept of vtables and vptrs here, but they are used to call the correct method implementation in this virtual dispatch context. The vtable is a static piece of data that basically only contains a function pointer for each method. With that, a reference to a trait object is basically represented as:

struct TraitObjectRef {
    data_ptr: *const (),
    vptr: *const (),
}

(这与C ++不同,在C ++中,抽象类的vptr存储在对象中.这两种方法都有其优点和缺点.)

(This is different from C++, where the vptr for abstract classes is stored within the object. Both approaches have advantages and disadvantages.)


实际上,可以通过具有最后一个字段为DST的结构来创建自己的DST.不过,这种情况很少见.一个突出的例子是std::path::Path.

It's actually possible to create your own DSTs by having a struct where the last field is a DST. This is rather rare, though. One prominent example is std::path::Path.

对自定义DST的引用或指针也是胖指针.附加数据取决于结构内部DST的类型.

A reference or pointer to the custom DST is also a fat pointer. The additional data depends on the kind of DST inside the struct.


RFC 1861 中,引入了extern type功能. Extern类型也是DST,但是指向它们的指针是 not 胖指针.或更确切地说,如RFC所述:

In RFC 1861, the extern type feature was introduced. Extern types are also DSTs, but pointers to them are not fat pointers. Or more exactly, as the RFC puts it:

在Rust中,指向DST的指针携带有关所指向对象的元数据.对于字符串和切片,这是缓冲区的长度,对于特征对象,这是对象的vtable.对于外部类型,元数据只是().这意味着指向extern类型的指针的大小与usize相同(即,它不是胖指针").

In Rust, pointers to DSTs carry metadata about the object being pointed to. For strings and slices this is the length of the buffer, for trait objects this is the object's vtable. For extern types the metadata is simply (). This means that a pointer to an extern type has the same size as a usize (ie. it is not a "fat pointer").

但是,如果您不与C接口交互,则可能永远不必处理这些外部类型.

But if you are not interacting with a C interface, you probably won't ever have to deal with these extern types.





上面,我们已经看到了不可变引用的大小.胖指针对可变引用,不变的原始指针和可变的原始指针的作用相同:

Above, we've seen the sizes for immutable references. Fat pointers work the same for mutable references, immutable raw pointers and mutable raw pointers:

size_of::<&[u32]>()       = 16
size_of::<&mut [u32]>()   = 16
size_of::<*const [u32]>() = 16
size_of::<*mut [u32]>()   = 16

这篇关于什么是“胖指针"?在Rust中?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆