% Призрачные данные (PhantomData)
При работе с небезопасным кодом мы часто можем попасть в ситуацию, когда типы
или времена жизни логически ассоциируются со структурой, но не являются на
самом деле частью конкретного поля. Особенно часто это происходит с временами
жизни. Например, Iter
для &'a [T]
описывается (примерно) так:
struct Iter<'a, T: 'a> {
ptr: *const T,
end: *const T,
}
Но, в связи с тем, что 'a
не используется внутри тела структуры, оно
безгранично. Из-за проблем, которые исторически возникли, безграничные времена
жизни и типы запрещено использовать в описании структур. Поэтому необходимо
как-то перестроить эти поля внутри тела. Чтобы проверки вариантности и удаления
выполнились корректно, это важно сделать правильно.
Используем PhantomData
, который является специальным маркерным типом.
PhantomData
не занимает места в памяти, а симулирует поле необходимого типа
для целей статического анализа. Считается, что так код менее подвержен ошибкам,
чем при явном указании необходимой вариантности системе типов, а заодно
предоставляется другая полезная информация, необходимая для проверки удаления.
Iter по логике содержит кучу &'a T
, поэтому именно ее мы и будем
симулировать PhantomData:
use std::marker;
struct Iter<'a, T: 'a> {
ptr: *const T,
end: *const T,
_marker: marker::PhantomData<&'a T>,
}
И все. Время жизни будет ограничено, и ваш итератор будет вариантен над 'a
и
T
. Все Просто Работает.
Важным примером также является Vec, который описывается (примерно) так:
struct Vec<T> {
data: *const T, // *const для вариантности!
len: usize,
cap: usize,
}
В отличие от предыдущего примера кажется, что все именно так, как мы хотим. Один обобщенный аргумент в Vec проявляется по крайней мере в одном поле. Идем дальше!
Не-а.
Анализатор удалений великодушно определит, что Vec
Для того, чтобы объяснить анализатору удалений, что мы на самом деле владеем значениями типа T, и, следовательно, можем удалять некоторые T во время удаления всего Vec, мы должны добавить дополнительное поле PhantomData, явно утверждая:
use std::marker;
struct Vec<T> {
data: *const T, // *const для ковариантности!
len: usize,
cap: usize,
_marker: marker::PhantomData<T>,
}
Сырые указатели, владеющие пространством в памяти - это настолько повсеместная
картина, что в стандартной библиотеке создали утилиту для этого, называемую
Unique<T>
, которая:
- оборачивает
*const T
для вариантности - включает в себя
PhantomData<T>
, - автоматически выводит Send/Sync, как будто T их реализует,
- помечает указатель NonZero для оптимизации нулевого указателя.