Stream: t-lang/wg-unsafe-code-guidelines

Topic: repr(transparent) and transmute


Lokathor (Jun 30 2019 at 03:26, on Zulip):

Is repr(transparent) required to transmute a newtype into its base type without UB?

I think the answer is "yes", but would like confirmation either way.

gnzlbg (Jun 30 2019 at 08:15, on Zulip):

No

gnzlbg (Jun 30 2019 at 08:16, on Zulip):

Transparent guarantees that the wrapper type has the same layout as the type being wrapped.

gnzlbg (Jun 30 2019 at 08:16, on Zulip):

If you want to transmute, you only need the size to match, and for the result to be valid (you don't need the same alignment, niches, call ABI).

RalfJ (Jun 30 2019 at 09:18, on Zulip):

well it's a newtype so the T is certainly the same

RalfJ (Jun 30 2019 at 09:19, on Zulip):

do we guarantee that struct NewType(T) and T have the same size and align? I think that's https://github.com/rust-lang/unsafe-code-guidelines/issues/34 and even within the UCG we didn't get consensus^^

RalfJ (Jun 30 2019 at 09:19, on Zulip):

so I'd actually answer @Lokathor's question with "yes"

gnzlbg (Jun 30 2019 at 10:04, on Zulip):

@RalfJ the only thing that transmute requires is same size, and that the result of the transmute is _valid_. repr(transparent) on the wrapper gives you that, but it is not _necessary_ to obtain that.

AFAICT:

struct Wrapper<T>(T);
fn foo<T>(x: Wrapper<T>) -> T
  where size_of::<T>() == size_of::<Wrapper<T>>()
{
    unsafe { transmute (x) }
}

is always safe: the result is always valid.

This _might_ fail to type-check if the Wrapper and T have different sizes, but that's about it.

For example, this would also be always safe:

#[repr(align(64))] struct OveralignedWrapper<T>(T);
fn foo<T>(x: OveralignedWrapper<T>) -> T
  where size_of::<T>() == size_of::<OveralignedWrapper<T>>()
{
    unsafe { transmute (x) }
}
RalfJ (Jun 30 2019 at 10:52, on Zulip):

okay with the size checks you are basically constraining the layout

RalfJ (Jun 30 2019 at 10:52, on Zulip):

did we agree in the UCG discussions that field offsets are the only "moving piece" in struct layout?

RalfJ (Jun 30 2019 at 10:52, on Zulip):

that's what you are assuming here, basically

Lokathor (Jun 30 2019 at 15:58, on Zulip):

Doesn't niches play in to validity?

RalfJ (Jun 30 2019 at 15:58, on Zulip):

they do. or rather the other way around.

RalfJ (Jun 30 2019 at 15:58, on Zulip):

validity "justifies" niches.

gnzlbg (Jun 30 2019 at 16:00, on Zulip):

The current stand sounds like layout is going to be: size, alignment, niches, field offsets, and call ABI
For the transmute, the alignment and the call ABI are irrelevant. The size is constrained. So that leaves niches and field offsets.

let w: Wrapper(T);
let x: &T = &w.0;

requires the field offsets of T and Wrapper<T>(T) to match under the constraint that both have the same size. Also, validity requires the T in the Wrapper to always be valid. The niches of both are also the same. A more interesting wrapper can have more niches than T: e.g. NonZero<T>, or Option<T>, where niches play a role.

Lokathor (Jun 30 2019 at 22:25, on Zulip):

those two lines there don't necessarily assert that the address of &w and &w.0 are the same, do they?

Also, one thing of note, a literal transmute of course ignores alignment because it gets pushed onto the stack at an aligned position of the input type and returns at an aligned position of the output type, but people often type pun references/pointers as well, so alignment still needs to be kept in mind in general when talking about type punning. Bit of my fault there for not being clear about the full picture on that one.

Lokathor (Jun 30 2019 at 22:28, on Zulip):

Also, NonNull<T> isn't actually wrapping a T so it doesn't really affect niche calculation, and Option<T> will never have more niches than T, it'll always have "some number less" than T itself does, right?

Nicole Mazzuca (Jun 30 2019 at 22:30, on Zulip):

It'll have |niches(Option<T>)| ≤ |niches(T)|

Nicole Mazzuca (Jun 30 2019 at 22:31, on Zulip):

I think it's likely that 0 ≤ |niches(T)| - |niches(Option<T>)| ≤ 1

Lokathor (Jun 30 2019 at 22:33, on Zulip):

that second line is hard to parse

Nicole Mazzuca (Jul 01 2019 at 01:48, on Zulip):

"the number of niches in Option<T> is either the same as, or one less than, the number of niches in T"

gnzlbg (Jul 01 2019 at 05:54, on Zulip):

Also, NonNull<T> isn't actually wrapping a T so it doesn't really affect niche calculation

Which is why I said NonZero<T>, and not NonNull<T>.

but people often type pun references/pointers as well, so alignment still needs to be kept in mind in general when talking about type punning.

Even when type punning, it is ok to have a T at an unaligned memory location, that's what repr(packed) does.

gnzlbg (Jul 01 2019 at 05:57, on Zulip):

When the niches of the types in the transmute do not match, the only thing you need to make sure of, is not creating an invalid value. So, e.g., you can always transmute a T into an Option<T>, but you can't always transmute an Option<T> into a T.

gnzlbg (Jul 01 2019 at 05:58, on Zulip):

You can transmute a NonZero<T> into a T, but you can't transmute all Ts into a NonZero<T>, etc.

gnzlbg (Jul 01 2019 at 06:01, on Zulip):

To write a safe wrapper that transmutes from T into NonZero<T>, you'd need to make sure that T is valid at NonZero<T>, e.g., by checking whether T is zero. Alternatively, you can transmute a T into a Option<NonZero<T>>, because that Option is always valid.

gnzlbg (Jul 01 2019 at 06:03, on Zulip):

When you use union field access to read a field, you are asserting that the field is valid, so need to take these things into account.

Lokathor (Jul 01 2019 at 06:58, on Zulip):

ah! NonZero, right

Last update: Nov 19 2019 at 18:35UTC