Я уже читал термин «толстый указатель» в нескольких контекстах, но я не уверен, что именно он означает и когда он используется в Rust. Указатель кажется вдвое больше обычного указателя, но я не понимаю почему. Это также имеет какое-то отношение к объектам-признакам.
91
Ответы:
Термин «толстый указатель» используется для обозначения ссылок и необработанных указателей на типы с динамическим размером (DST) - срезы или объекты-признаки. Толстый указатель содержит указатель плюс некоторую информацию, которая делает DST «полным» (например, длину).
Наиболее часто используемые типы в Rust не являются DST, но имеют фиксированный размер, известный во время компиляции. Эти типы реализации на
Sized
черту . Даже типы, которые управляют буфером кучи динамического размера (например,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]>());
Это печатает (с некоторой очисткой):
Итак, мы видим, что ссылка на обычный тип, например,
u32
имеет размер 8 байт, как и ссылка на массив[u32; 2]
. Эти два типа не относятся к летнему времени. Но, как и[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>());
Это печатает (с некоторой очисткой):
Опять же,
&Cat
имеет размер всего 8 байт, потому чтоCat
это нормальный тип. Ноdyn Animal
это объект-признак и, следовательно, динамический размер. Таким образом,&dyn Animal
его размер составляет 16 байт.В случае типажных объектов дополнительные данные, которые завершают DST, являются указателем на vtable (vptr). Я не могу полностью объяснить концепцию vtables и vptrs здесь, но они используются для вызова правильной реализации метода в этом контексте виртуальной диспетчеризации. Таблица vtable - это статическая часть данных, которая в основном содержит только указатель на функцию для каждого метода. При этом ссылка на объект-признак в основном представлена как:
struct TraitObjectRef { data_ptr: *const (), vptr: *const (), }
(Это отличается от C ++, где vptr для абстрактных классов хранится внутри объекта. Оба подхода имеют свои преимущества и недостатки.)
Пользовательские DST
На самом деле можно создавать свои собственные DST, имея структуру, в которой последнее поле является DST. Однако это довольно редко. Один из ярких примеров
std::path::Path
.Ссылка или указатель на настраиваемое летнее время также является жирным указателем. Дополнительные данные зависят от типа DST внутри структуры.
Исключение: внешние типы
В RFC 1861 эта
extern type
функция была представлена. Типы Extern также являются DST, но указатели на них не являются жирными указателями. Или, точнее, как сказано в RFC:Но если вы не взаимодействуете с интерфейсом C, вам, вероятно, никогда не придется иметь дело с этими внешними типами.
Выше мы видели размеры неизменяемых ссылок. Жирные указатели работают одинаково для изменяемых ссылок, неизменяемых исходных указателей и изменяемых исходных указателей:
size_of::<&[u32]>() = 16 size_of::<&mut [u32]>() = 16 size_of::<*const [u32]>() = 16 size_of::<*mut [u32]>() = 16
источник