Skip to content

Is Rc’s and Arc’s data_offset correct? #62522

Closed

Description

@SimonSapin

In the implementation of Arc, we find:

rust/src/liballoc/sync.rs

Lines 258 to 269 in 09ab31b

struct ArcInner<T: ?Sized> {
strong: atomic::AtomicUsize,
// the value usize::MAX acts as a sentinel for temporarily "locking" the
// ability to upgrade weak pointers or downgrade strong ones; this is used
// to avoid races in `make_mut` and `get_mut`.
weak: atomic::AtomicUsize,
data: T,
}

rust/src/liballoc/sync.rs

Lines 2288 to 2304 in 09ab31b

/// Computes the offset of the data field within ArcInner.
unsafe fn data_offset<T: ?Sized>(ptr: *const T) -> isize {
// Align the unsized value to the end of the ArcInner.
// Because it is ?Sized, it will always be the last field in memory.
let align = align_of_val(&*ptr);
let layout = Layout::new::<ArcInner<()>>();
(layout.size() + layout.padding_needed_for(align)) as isize
}
/// Computes the offset of the data field within ArcInner.
///
/// Unlike [`data_offset`], this doesn't need the pointer, but it works only on `T: Sized`.
fn data_offset_sized<T>() -> isize {
let align = align_of::<T>();
let layout = Layout::new::<ArcInner<()>>();
(layout.size() + layout.padding_needed_for(align)) as isize
}

(And similarly in rc.rs with struct RcBox.)

Note the comment: Because it is ?Sized, it will always be the last field in memory.

Is this actually true, even for instantiations where T happens to be Sized? Does the language guarantee that there exist no type Foo such that the fields of ArcInner<Foo> would be re-ordered?

Or do we need to add #[repr(C)] to ArcInner and RcBox?

CC @rust-lang/wg-unsafe-code-guidelines

Activity

  1. hanna-kruppe commented on Jul 9, 2019

    @hanna-kruppe
    Contributor

    If this were user-facing code it would be a clear case of "no that's not specified, don't assume that". However, I believe rustc does currently honor this assumption and libstd can of course rely on rustc implementation details. @eddyb would know best.

    Is this actually true, even for instantiations where T happens to be Sized?

    Note that because unsizing happens with "reorganizing" the struct, the struct with a sized T has to be laid out "as if unsized" from the start. For example, Rc<u8> and Rc<u128> and many other Rc<T: Sized> can all be casted to Rc<dyn Debug> without touching the RcInner, the differences in layout between those are quite constrained (limited to different amounts of paddding to respect alignment, I think?).

    This reasoning wouldn't apply if there was a type that couldn't ever be involved in unsizing, but I don't think the compiler can ever realistically rule that out locally, and I'm rather certain that it doesn't today.

  2. SimonSapin commented on Jul 9, 2019

    @SimonSapin
    ContributorAuthor

    Ah that’s a good point. The constraint that layout cannot change while unsizing is probably enough indeed.

  3. Centril commented on Jul 9, 2019

    @Centril
    Contributor

    If this were user-facing code it would be a clear case of "no that's not specified, don't assume that". However, I believe rustc does currently honor this assumption and libstd can of course rely on rustc implementation details.

    We should probably add a comment to that effect so "don't assume that" is clearer to everyone.

  4. RalfJung commented on Jul 10, 2019

    @RalfJung
    Member

    We should probably add a comment to that effect so "don't assume that" is clearer to everyone.

    We should! See rust-lang/unsafe-code-guidelines#90.

  5. added a commit that references this issue on Dec 22, 2019
    ca528fc
  6. RalfJung commented on Aug 13, 2023

    @RalfJung
    Member

    FWIW these are repr(C) nowadays so this got properly fixed at some point.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    T-langRelevant to the language teamT-libs-api[DEPRECATED; DO NOT USE]

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions