Currently, the Arc
, Rc
, and Weak
structs do not have a #[repr(…)]
; they don't have a guaranteed type layout. However, all of their constituent types already do have guaranteed layouts. In particular, in rust-lang#68099 their inner ArcInner
and RcBox
types were marked as #[repr(C)]
to ensure certain transmutations were/remained sound.
Type Details
Each of these structs contains a single NonNull
pointer to their backing allocation, with the strong references also containing a PhantomData
(but that is zero-sized so it's not layout-relevant).
pub struct Arc<T: ?Sized> {
ptr: NonNull<ArcInner<T>>,
phantom: PhantomData<ArcInner<T>>,
}
pub struct Weak<T: ?Sized> {
ptr: NonNull<ArcInner<T>>,
}
#[rustc_insignificant_dtor]
pub struct Rc<T: ?Sized> {
ptr: NonNull<RcBox<T>>,
phantom: PhantomData<RcBox<T>>,
}
pub struct Weak<T: ?Sized> {
ptr: NonNull<RcBox<T>>,
}
The Rust reference tells us that pointers to a given type have a consistent layout, and NonNull
is #[repr(transparent)]
so it will as well. It has a zero niche, but that doesn't affect its own layout, it just allows that single unused bit pattern in its representation to be used by a containing enum. Adding #[repr(transparent)]
to the outer structs would guarantee that they also share this representation.
#[repr(transparent)]
#[rustc_layout_scalar_valid_range_start(1)]
#[rustc_nonnull_optimization_guaranteed]
pub struct NonNull<T: ?Sized> {
pointer: *const T,
}
#[repr(C)]
struct ArcInner<T: ?Sized> {
strong: atomic::AtomicUsize,
weak: atomic::AtomicUsize,
data: T,
}
#[repr(C)]
struct RcBox<T: ?Sized> {
strong: Cell<usize>,
weak: Cell<usize>,
value: T,
}
Would it be reasonable to add #[repr(transparent)]
to those structs so that they could also have guaranteed memory layouts? From my (shallow) understanding of rustc
, I expect the current generated layouts are already like that in practice, so it shouldn't affect performance or code generation at all, but the layout could hypothetically change in future compiler versions, so it's not safe to rely on.
This would allow &Arc<T>
/&Rc<T>
references to be safely transmuted to corresponding &Weak<T>
references. I've wanted to do a couple times when I've had an Arc<T>
but I'm calling a function that expects a &Weak<T>
. The intermediate Arc::downgrade()
and associated weak reference count changes feel wasteful when I know the types are the same internally, and the static guarantees carried by Weak<T>
are strictly weaker than those carried by Arc<T>
. (This was originally asked about on Stack Overflow).
Perhaps this could be exposed as a safe Arc
/Rc::as_weak
associated function, something like this…
impl<T: ?Sized> Rc<T> {
/// Convert a reference to an [`Rc`] into a reference to a [`Weak`] of
/// of the same type.
///
/// This is a type-only operation; it doesn't modify the inner reference
/// counts.
///
/// # Examples
///
/// ```
/// use std::rc::{Rc, Weak};
///
/// let five: &Rc<i32> = &Rc::new(5);
///
/// let weak_five: &Weak<i32> = Rc::as_weak(five);
///
/// ```
pub const fn as_weak<'a>(this: &'a Self) -> &'a Weak<T> {
unsafe { mem::transmute::<&'a Rc<T>, &'a Weak<T>>(this) }
}
}
…or maybe that could be left as an exercise for library authors.
Is this a reasonable idea? Am I missing an obvious flaw? Thanks for reading.