Исправление: Ответ ниже по-прежнему в общем случае, но в случае MaybeUninit
есть несколько удобных особых случаев с разметкой памяти, которые делают это действительно безопасным:
Во-первых, документация для MaybeUninit
имеет раздел макет , в котором говорится, что
MaybeUninit<T>
гарантированно имеет тот же размер и выравнивание, что и T
.
Во-вторых, в справочнике по языку говорится об разметках массивов :
Массивы расположены так, что элемент nth
массива смещенот начала массива на n * the size of the type
байта.Массив [T; n]
имеет размер size_of::<T>() * n
и такое же выравнивание T
.
Это означает, что макет MaybeUninit<[T; n]>
и макет [MaybeUninit<T>; n]
совпадают.
Оригинальный ответ:
Из того, что я могу сказать, это одна из тех вещей, которые могут работать, но не гарантируются, и может зависеть от поведения компилятора или платформы.
MaybeUninit
определяется следующим образом в источнике тока :
#[allow(missing_debug_implementations)]
#[unstable(feature = "maybe_uninit", issue = "53491")]
pub union MaybeUninit<T> {
uninit: (),
value: ManuallyDrop<T>,
}
Поскольку он не помечен атрибутом #[repr]
(в отличие от, например, ManuallyDrop
), он находится в представлении по умолчанию, о котором ссылка говорит это :
Номинальные типы без атрибута repr имеют представление по умолчанию.Неофициально это представление также называется представлением ржавчины.
Нет никаких гарантий размещения данных, сделанных этим представлением.
Для преобразования из *От 1067 * до [Wrapper<T>]
, это должен быть случай, когда расположение памяти Wrapper<T>
равно точно так же, как расположение памяти T
.Это относится ко многим оберткам, таким как ранее упомянутый ManuallyDrop
, и они обычно помечаются атрибутом #[repr(transparent)]
.
Но в этом случае это не обязательно верно.Поскольку ()
является типом нулевого размера, вполне вероятно, что компилятор будет использовать ту же схему памяти для T
и MaybeUninit<T>
(и именно поэтому он работает для вас), но также возможно, что компилятор решитиспользовать какую-то другую структуру памяти (например, в целях оптимизации), в этом случае преобразование больше не будет работать.
В качестве конкретного примера, компилятор может использовать следующую схему памяти для MaybeUninit<T>
:
+---+---+...+---+
| T | b | where b is "is initialized" flag
+---+---+...+---+
Согласно приведенной выше цитате, компилятору разрешено это делать.В этом случае [MaybeUninit<T>]
и MaybeUninit<[T]>
имеют разные макеты памяти, поскольку MaybeUninit<[T]>
имеет один b
для всего массива, в то время как [MaybeUninit<T>]
имеет один b
для каждого MaybeUninit<T>
в массиве:
MaybeUninit<[T]>:
+---+...+---+---+...+---+...+---+...+---+---+
| T[0] | T[1] | … | T[n-1] | b |
+---+...+---+---+...+---+...+---+...+---+---+
Total size: n * size_of::<T>() + 1
[MaybeUninit<T>]
+---+...+---+----+---+...+---+----+...+---+...+---+------+
| T[0] |b[0]| T[1] |b[1]| … | T[n-1] |b[n-1]|
+---+...+---+----+---+...+---+----+...+---+...+---+------+
Total size: (n + 1) * size_of::<T>()