Já li o termo "apontador gordo" em vários contextos, mas não tenho certeza do que significa exatamente e quando é usado no Rust. O ponteiro parece ter o dobro do tamanho de um ponteiro normal, mas não entendo por quê. Também parece ter algo a ver com objetos de características.
95
Respostas:
O termo "ponteiro gordo" é usado para se referir a referências e ponteiros brutos para tipos de tamanho dinâmico (DSTs) - fatias ou objetos de traço. Um ponteiro grande contém um ponteiro mais algumas informações que tornam o DST "completo" (por exemplo, o comprimento).
Os tipos mais comumente usados no Rust não são DSTs, mas têm um tamanho fixo conhecido em tempo de compilação. Esses tipos implementam o
Sized
traço . Mesmo os tipos que gerenciam um buffer de heap de tamanho dinâmico (comoVec<T>
) sãoSized
porque o compilador sabe o número exato de bytes que umaVec<T>
instância ocupará na pilha. Atualmente, existem quatro tipos diferentes de DSTs em Rust.Fatias (
[T]
estr
)O tipo
[T]
(para qualquer umT
) é dimensionado dinamicamente (assim como o tipo especial de "segmento de string"str
). É por isso que normalmente você só o vê como&[T]
ou&mut [T]
, ou seja, atrás de uma referência. Esta referência é um denominado "apontador gordo". Vamos checar:dbg!(size_of::<&u32>()); dbg!(size_of::<&[u32; 2]>()); dbg!(size_of::<&[u32]>());
Isso imprime (com alguma limpeza):
Portanto, vemos que uma referência a um tipo normal como
u32
tem 8 bytes de tamanho, pois é uma referência a um array[u32; 2]
. Esses dois tipos não são DSTs. Mas, como[u32]
é um DST, a referência a ele é duas vezes maior. No caso de fatias, os dados adicionais que "completam" o DST são simplesmente o comprimento. Então, pode-se dizer que a representação de&[u32]
é algo assim:struct SliceRef { ptr: *const u32, len: usize, }
Objetos de traço (
dyn Trait
)Ao usar características como objetos de características (ou seja, tipo apagado, despachado dinamicamente), esses objetos de características são DSTs. Exemplo:
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>());
Isso imprime (com alguma limpeza):
Novamente,
&Cat
tem apenas 8 bytes porqueCat
é um tipo normal. Masdyn Animal
é um objeto de característica e, portanto, dimensionado dinamicamente. Como tal,&dyn Animal
tem 16 bytes de tamanho.No caso de objetos de traço, os dados adicionais que completam o DST são um ponteiro para a vtable (vptr). Não posso explicar completamente o conceito de vtables e vptrs aqui, mas eles são usados para chamar a implementação do método correto neste contexto de envio virtual. O vtable é um dado estático que basicamente contém apenas um ponteiro de função para cada método. Com isso, uma referência a um objeto de traço é basicamente representada como:
struct TraitObjectRef { data_ptr: *const (), vptr: *const (), }
(Isso é diferente de C ++, onde o vptr para classes abstratas é armazenado dentro do objeto. Ambas as abordagens têm vantagens e desvantagens.)
DSTs personalizados
Na verdade, é possível criar seus próprios DSTs por meio de uma estrutura em que o último campo é um DST. Porém, isso é bastante raro. Um exemplo importante é
std::path::Path
.Uma referência ou ponteiro para o horário de verão personalizado também é um ponteiro grande. Os dados adicionais dependem do tipo de DST dentro da estrutura.
Exceção: tipos externos
No RFC 1861 , o
extern type
recurso foi introduzido. Tipos externos também são DSTs, mas os ponteiros para eles não são ponteiros gordos. Ou mais exatamente, como diz o RFC:Mas se você não estiver interagindo com uma interface C, provavelmente nunca terá que lidar com esses tipos externos.
Acima, vimos os tamanhos para referências imutáveis. Ponteiros de gordura funcionam da mesma forma para referências mutáveis, ponteiros brutos imutáveis e ponteiros brutos mutáveis:
size_of::<&[u32]>() = 16 size_of::<&mut [u32]>() = 16 size_of::<*const [u32]>() = 16 size_of::<*mut [u32]>() = 16
fonte