Arc is #[repr(Rust)], which means transmuting to/from it is only sound to #[repr(transparent)] wrappers of it. But even if Arc were #[repr(transparent)] itself (guaranteed to have the layout of a nonnull pointer to T†),
ArcInner is #[repr(Rust)], so transmuting (or pointer casting) between ArcInner<T> and ArcInner<U> is also UB for any T/U pair.
† though it is not necessarily an actual pointer to T, it just has the same layout.
The operation I want to make possible here is to drop the last Arc without dropping the payload. Or, transmute<Arc<T>, Arc<ManuallyDrop<T>>.
This can be guaranteed at a library level without any new language guarantees. All that std has to do is to mark Arc as #[repr(transparent)] (guaranteeing Arc to be laid out like ptr::NonNull<T>), and to mark ArcInner as #[repr(C)] (guaranteeing the field order to be strong, weak, data). This would at a language level make this operation defined, and if Arc's documentation promised it defined, make it defined at a library level. (Or, alternatively, just offer an into_manually_drop API to encapsulate the transmute.)
For ManuallyDrop/MaybeUninit I'd first assume this transmute would be compatible, but clearly it's a question that has a different answer than what would make immediate sense, and the example with Option in MaybeUninit clearly shows why it is in general not true, that it's possible to do this.
I don't think that layout guarantees are necessary. Only that Arc::from_raw should be able to take a raw pointer that was cast from Arc::into_raw to a layout compatible type (like T -> ManuallyDrop<T>). This amounts to changing the documentation for Arc::from_raw, because we can rely on the layout of ArcInner inside std. I don't think that we want to make Arc ffi safe directly, which us what the layout guarantees would do.
This would still allow you to precisely control the lifetime of the Arc, but not using transmute, and instead using from/into_raw
That would just require marking ArcInner as #[repr(C)].
Note that std currently only relies on the layout of ArcInner (equivalently RcBox):
Only padding between the ref counts and the data, and
The data is laid out last (because it is ?Sized).
Std does not actually rely on the order of the strong and weak fields.
Requiring Arc::from_raw(Arc::into_raw(arc) as *const ManuallyDrop<T>) to work is effectively requiring that ArcInner has the #[repr(C)] layout, so I'd be more comfortable guaranteeing it to work if we actually do apply #[repr(C)] to the ArcInner. (It's not like it's possible to do better while the ?Sized member is last.)
Huh, I didn't realize, but this (the from_raw(into_raw) version) was actually documented as sound in #68099 in February. That PR marked the inner types as #[repr(C)] and specified the type requirements of from_raw as
The raw pointer must have been previously returned by a call to Arc<U>::into_raw where U must have the same size and alignment as T . This is trivially true if U is T . Note that if U is not T but has the same size and alignment, this is basically like transmuting references of different types. See mem::transmute for more information on what restrictions apply in this case.