Это почти всегда небезопасно.
Arc<T>
- это просто указатель на выделенную кучу структуру, которая примерно выглядит как
struct ArcInner<T: ?Sized> {
strong: atomic::AtomicUsize,
weak: atomic::AtomicUsize,
data: T, // You get a raw pointer to this element
}
into_raw()
дает указатель на элемент data
. Реализация Arc::from_raw()
принимает такой указатель, предполагает , что это указатель на data
-элемент в ArcInner<T>
, возвращается в память и предполагает , чтобы найти ArcInner<T>
там. Это предположение зависит от структуры памяти T
, в частности от ее выравнивания и, следовательно, от точного размещения в ArcInner
.
Если вы вызываете into_raw()
на Arc<U>
, а затем вызываете from_raw()
как если это было Arc<V>
, где U
и V
отличаются выравниванием, вычисление смещения, где U
/ V
находится в ArcInner
, будет неправильным, и вызов .clone()
повредит структура данных. Разыменование T
поэтому не требуется для запуска небезопасной памяти .
На практике это может не быть проблемой: поскольку data
является третьим элементом после двух usize
-элементов большинство T
, вероятно, будут выровнены таким же образом. Однако, если реализация stdlib изменится или вы в конечном итоге скомпилируете для платформы, где это предположение неверно, реконструкция Arc<V>::from_raw
, созданного Arc<U>
, где расположение памяти в V
и U
будет другим, будет быть небезопасным и безумным sh.
Обновление:
Подумав об этом еще немного, я понизил свой голос с "может быть безопасным, но осторожным" до "скорее всего небезопасным" потому что я всегда могу сделать
#[repr(align(32))]
struct Foo;
let foo = Arc::new(Foo);
В этом примере Foo
будет выровнен до 32 байтов, делая размер ArcInner<Foo>
32 байта (8 + 8 + 16 + 0), в то время как ArcInner<()>
всего 16 байтов (8 + 8 + 0 + 0). Поскольку невозможно определить, каково выравнивание T
после стирания типа, невозможно восстановить действительный Arc
.
Существует аварийный люк, который может быть безопасным в практика: Оборачивая T
в другой Box
, раскладка ArcInner<T>
всегда одинакова. Чтобы навязать это любому пользователю, вы можете сделать что-то вроде
struct ArcBox<T>(Arc<Box<T>>)
и реализовать на нем Deref
. Использование ArcBox
вместо Arc
заставляет расположение памяти ArcInner
всегда оставаться одинаковым, потому что T
находится позади другого указателя. Это, однако, означает, что любой доступ к T
требует двойной разыменования, что может негативно повлиять на производительность.