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