# Pre-RFC: Transparent unions

**URL:** https://internals.rust-lang.org/t/pre-rfc-transparent-unions/9441
**Category:** language design
**Created:** [February 14, 2019, 6:40am UTC](https://internals.rust-lang.org/t/pre-rfc-transparent-unions/9441 "2019-02-14T06:40:36Z")
**Posts on this page:** 15
**Page:** 1

<div class="post-metadata">

### Author: ![mjbshaw](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/mjbshaw/32/5103_2.png) [@mjbshaw](https://internals.rust-lang.org/u/mjbshaw)
#### Post date: [February 14, 2019, 6:40am UTC](https://internals.rust-lang.org/t/pre-rfc-transparent-unions/9441/1 "2019-02-14T06:40:36Z")

</div>

- Feature Name: transparent\_unions
- Start Date: 2019-02-13
- RFC PR:
- Rust Issue:

# Summary

Allow `#[repr(transparent)]` on `union`s that have exactly one non-zero-sized field (just like `struct`s).

# Motivation

Some `union` types are thin newtype-style wrappers around another type, like `ManuallyDrop<T>` and `MaybeUninit<T>`. Both of these types are intended to be used in the same places as `T`, but without being `#[repr(transparent)]` the actual compatibility between these types and `T` is left unspecified.

Making these types `#[repr(transparent)]` would be useful in certain cases. The following example is from the author's own experience, and is not meant to be the canonical/best motivation of this feature. It is just an example with which the author is most immediately familiar with.

The [objrs](https://gitlab.com/objrs/objrs) crate defines macros that transform pure Rust code into being ABI-compatible with Objective-C, allowing the two languages to interoperate directly without any intermediary bridges. For various reasons that will be ommitted for brevity, sending a message to an object from Rust requires going through a `#[inline(never)]` trampoline:

```rust
#[inline(always)]
pub fn print(x: u32, y: u32, z: u32) {
    return __objrs_msg_send_print(core::mem::MaybeUninit::uninitialized(),
                                  core::mem::MaybeUninit::uninitialized(),
                                  x, y, z);
}

#[inline(never)]
#[doc(hidden)]
#[allow(non_upper_case_globals)]
extern "C" fn __objrs_msg_send_print(_: core::mem::MaybeUninit<objrs::runtime::Class>,
                                     _: core::mem::MaybeUninit<*const [u8; 6usize]>,
                                     x: u32, y: u32, z: u32) {
    extern "C" {
        #[link_name = "OBJC_CLASS_$_ClassName"]
        static CLASS: objrs::runtime::objc_class;
    }
    #[link_section = " __DATA,__ objc_classrefs,regular,no_dead_strip"]
    #[export_name = "\x01L_OBJC_CLASSLIST_REFERENCES_$_.ClassName"]
    static REF: objrs::runtime::Class = objrs::runtime::Class(unsafe {
        &CLASS as *const _
    });
    let this = unsafe { core::ptr::read_volatile(&REF as *const _) };

    #[link_section = " __TEXT,__ objc_methname,cstring_literals"]
    #[export_name = "\x01L_OBJC_METH_VAR_NAME_.__objrs_meth.print"]
    static METH_NAME: [u8; 6usize] = *b"print\x00";

    #[link_section = " __DATA,__ objc_selrefs,literal_pointers,no_dead_strip"]
    #[export_name = "\x01L_OBJC_SELECTOR_REFERENCES_.__objrs_sel.print"]
    static SEL_REF: &'static [u8; 6usize] = &METH_NAME;

    let msg_send: unsafe extern "C" fn(objrs::runtime::Class,
                                       *const [u8; 6usize], u32, u32, u32);
    msg_send = unsafe {
        core::mem::transmute(objrs::runtime::objc_msgSend as *const ())
    };
    let sel = unsafe { core::ptr::read_volatile(&SEL_REF as *const _) } as *const _;
    return unsafe { msg_send(this, sel, x, y, z) };
}

```

The public user-facing function is `print`, which delegates to the trampoline ` __objrs_msg_send_print`. The use of `core::mem::MaybeUninit` for the first two arguments to `__ objrs_msg_send_print` allows the compiler to better optimize the trampoline since the parameters fully match up between `__objrs_msg_send_print` and `msg_send`. No register spilling or shufflig is required. [This generates the following optimal assembly](https://rust.godbolt.org/z/bfGTUJ):

```rust
__objrs_msg_send_print:
    movq L_OBJC_CLASSLIST_REFERENCES_$_.ClassName@GOTPCREL(%rip), %rax
    movq (%rax), %rdi
    movq L_OBJC_SELECTOR_REFERENCES_.__objrs_sel.print@GOTPCREL(%rip), %rax
    movq (%rax), %rsi
    jmpq *objc_msgSend@GOTPCREL(%rip)

```

If you remove the two `MaybeUninit` parameters (so the arguments to `print` and `__objrs_msg_send_print` are identical), [suboptimal register shuffling is required](https://rust.godbolt.org/z/4scg4q):

```rust
example::__objrs_msg_send_print:
    movl %edx, %r8d
    movl %esi, %ecx
    movl %edi, %edx
    movq L_OBJC_CLASSLIST_REFERENCES_$_.ClassName@GOTPCREL(%rip), %rax
    movq (%rax), %rdi
    movq L_OBJC_SELECTOR_REFERENCES_.__objrs_sel.print@GOTPCREL(%rip), %rax
    movq (%rax), %rsi
    jmpq *objc_msgSend@GOTPCREL(%rip)

```

This kind of optimization is only possible if `MaybeUninit` has the same representation/ABI as its underlying type. Making `MaybeUninit` `#[repr(transparent)]` would make optimizations like this much more reliable.

More generally, these `union`s are wrappers around a type `T` that are intended to change its behavior but not its' ABI or representation. Permitting `#[repr(transparent)]` on `union`s would allow them to better fulfill that end.

# Guide-level explanation

A `union` may be `#[repr(transparent)]` in exactly the same conditions in which a struct may be `#[repr(transparent)]`. Some concrete illustrations follow.

A union may be `#[repr(transparent)]` if it has exactly one non-zero-sized field:

```rust
// This union has the same representation as `usize`.
#[repr(transparent)]
union CustomUnion {
    field: usize,
    nothing: (),
}

```

If the `union` is generic over `T` and has a field of type `T`, it may also be `#[repr(transparent)]` (even if `T` is a zero-sized type):

```rust
// This union has the same representation as `T`.
#[repr(transparent)]
pub union GenericUnion<T: Copy> { // Unions with non-`Copy` fields are unstable.
    pub field: T,
    pub nothing: (),
}

// This is okay even though `()` is a zero-sized type.
pub const THIS_IS_OKAY: GenericUnion<()> = GenericUnion { field: () };

```

# Reference-level explanation

The logic controlling whether a `union` of type `U` may be `#[repr(transparent)]` should match the logic controlling whether a `struct` of type `S` may be `#[repr(transparent)]` (assuming `U` and `S` have the same generic parameters and fields).

# Drawbacks

- `#[repr(transparent)]` on a `union` is of limited use. There are cases where it is useful, but they're not common and some users might unnecessarily apply `#[repr(transparent)]` to a `union`.

# Rationale and alternatives

It would be nice to make `ManuallyDrop<T>` and `MaybeUninit<T>` both `#[repr(transparent)]`. Both those types are `union`s, and thus this RFC is required in order to allow making those types transparent.

Of course, the standard "do nothing" alternative exists. Rust doesn't strictly _require_ this feature. But it would benefit from this, so the "do nothing" alternative is undesirable.

# Prior art

See [the discussion on RFC #1758](https://github.com/rust-lang/rfcs/pull/1758) (which introduced `#[repr(transparent)]`) for some discussion on applying the attribute to a `union`. A summary of the discussion:

> [Specify #[repr(transparent)] by nox · Pull Request #1758 · rust-lang/rfcs · GitHub](https://github.com/rust-lang/rfcs/pull/1758#discussion_r80436621) **nagisa:**"Why not univariant unions and enums?" **nox:**"I tried to be conservative for now given I don't have a use case for univariant unions and enums in FFI context."

> [Specify #[repr(transparent)] by nox · Pull Request #1758 · rust-lang/rfcs · GitHub](https://github.com/rust-lang/rfcs/pull/1758#issuecomment-254872520) **eddyb:**"I found another important usecase: for `ManuallyDrop<T>`, to be useful in arrays (i.e. small vector optimizations), it needs to have the same layout as `T` and AFAICT `#[repr(C)]` is not guaranteed to do the right thing" **retep998:**"So we'd need to be able to specify `#[repr(transparent)]` on unions?" **eddyb:**"That's the only way to be sure AFAICT, yes."

> [Specify #[repr(transparent)] by nox · Pull Request #1758 · rust-lang/rfcs · GitHub](https://github.com/rust-lang/rfcs/pull/1758#issuecomment-274670231) **joshtriplett:**"In terms of interactions with other features, I think this needs to specify what happens if you apply it to a union with one field, a union with multiple fields, a struct (tuple or otherwise) with multiple fields, a single-variant enum with one field, an enum struct variant where the enum uses `repr(u32)` or similar. The answer to some of those might be "compile error", but some of them (e.g. the union case) may potentially make sense in some contexts."

> [Specify #[repr(transparent)] by nox · Pull Request #1758 · rust-lang/rfcs · GitHub](https://github.com/rust-lang/rfcs/pull/1758#issuecomment-290757356) **pnkfelix:**"However, I personally do not think we need to expand the scope of the feature. So I am okay with leaving it solely defined on `struct`, and leave `union`/`enum` to a follow-on RFC later. (Much the same with a hypothetical `newtype` feature.)"

In summary, many of the questions regarding `#[repr(transparent)]` on a `union` were the same as applying it to a multi-field `struct`. These questions have since been answered, and I see no problems with applying those same answers to `union`.

# Unresolved questions

None (yet).

# Future possibilities

Univariant `enum`s are ommitted from this RFC in an effort to keep the scope small and avoid unnecessary bikeshedding. A future RFC could explore `#[repr(transparent)]` on a univariant `enum`.

---

<div class="post-metadata">

### Author: ![scottmcm](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/scottmcm/32/2355_2.png) [@scottmcm](https://internals.rust-lang.org/u/scottmcm)
#### Post date: [February 14, 2019, 6:53am UTC](https://internals.rust-lang.org/t/pre-rfc-transparent-unions/9441/2 "2019-02-14T06:53:39Z")

</div>

> [@mjbshaw](#):
>
> A `union` may be `#[repr(transparent)]` in exactly the same conditions in which a struct may be `#[repr(transparent)]` .

I was worried it'd be something more complicated, but if it's as simple as that this sounds great!

---

<div class="post-metadata">

### Author: ![RalfJung](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/ralfjung/32/2415_2.png) [@RalfJung](https://internals.rust-lang.org/u/RalfJung)
#### Post date: [February 14, 2019, 8:15am UTC](https://internals.rust-lang.org/t/pre-rfc-transparent-unions/9441/3 "2019-02-14T08:15:33Z")

</div>

Definitely in favor!

One correct though: `ManuallyDrop` is not a union, and it already is `repr(transparent)`.

---

<div class="post-metadata">

### Author: ![SimonSapin](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/simonsapin/32/3158_2.png) [@SimonSapin](https://internals.rust-lang.org/u/SimonSapin)
#### Post date: [February 14, 2019, 1:36pm UTC](https://internals.rust-lang.org/t/pre-rfc-transparent-unions/9441/4 "2019-02-14T13:36:44Z")

</div>

Is `MaybeUninitialized` the only use case for a union with one non-zero-size field? (Though I’m not sure what this would say. Is making at a special case better or worse than extending the rule to other such `union`s, however unlikely they are?)

---

<div class="post-metadata">

### Author: ![hanna-kruppe](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/hanna-kruppe/32/6540_2.png) [@hanna-kruppe](https://internals.rust-lang.org/u/hanna-kruppe)
#### Post date: [February 14, 2019, 2:15pm UTC](https://internals.rust-lang.org/t/pre-rfc-transparent-unions/9441/5 "2019-02-14T14:15:13Z")

</div>

I am very confused by the example given. I apologize for digging into something explicitly not intended to be “canonical” or “the best example”, but I do not see any strong relation to the pre-RFC at all. If I understand correctly, then

- the `MaybeUninit` arguments are not used at all, neither by callers nor callees, they are only added to make the actually-used arguments end up in more desirable registers
- this effect could be achieved more naturally with more direct control over the ABI of `__objrs_msg_send_print`
- alternatively it could also be achieved with a different (non-union) type as well
- The status quo already achieves the codegen you want
- Though the current codegen is not guaranteed, if it ever changes then objrs will just take a performance hit, not start hitting UB
  - (Worth noting that the codegen guarantee of `repr(transparent)` is about _ABI compatibility_, not about the actual thing you care about, good register allocation.)

If that’s correct, then I feel like this example is not just “not the best example” but at best tangentially related to `repr(transparent)`, and I’d really like to see an example for which `repr(transparent)` is actually important.

---

<div class="post-metadata">

### Author: ![comex](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/comex/32/2587_2.png) [@comex](https://internals.rust-lang.org/u/comex)
#### Post date: [February 14, 2019, 10:18pm UTC](https://internals.rust-lang.org/t/pre-rfc-transparent-unions/9441/6 "2019-02-14T22:18:59Z")

</div>

Don’t want to bikeshed too much, but an alternative would be to allow `#[repr(transparent)]` on any union, and give it the representation of its first member or something (which would have to be large enough). Thus, the following union would have the ABI of a `u64`, which would make it a way to pass around a pair of `u32`s that’s more efficient in certain cases:

```rust
#[repr(transparent)]
union Foo {
    one_u64: u64,
    two_u32s: TwoU32s,
}
#[repr(C)]
struct TwoU32s(u32, u32);

```

Edit: Or if, say, you ran out of registers for a function’s integer arguments, and instead of putting the rest on the stack as usual, you decide you want to stick them in floating-point registers for some reason 🙂 :

```rust
#[repr(transparent)]
union Bar {
    floating: f64,
    integer: u64,
}

```

---

<div class="post-metadata">

### Author: ![mjbshaw](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/mjbshaw/32/5103_2.png) [@mjbshaw](https://internals.rust-lang.org/u/mjbshaw)
#### Post date: [February 15, 2019, 4:54am UTC](https://internals.rust-lang.org/t/pre-rfc-transparent-unions/9441/7 "2019-02-15T04:54:12Z")

</div>

> [@RalfJung](#):
>
> One correct though: `ManuallyDrop` is not a union, and it already is `repr(transparent)` .

Oh man, I didn't realize it had changed. [It used to be a union](https://doc.rust-lang.org/1.26.1/src/core/mem.rs.html#950). Thanks for the heads up. I'll update my local copy of the text to remove mention of `ManuallyDrop`.

> [@SimonSapin](#):
>
> Is `MaybeUninitialized` the only use case for a union with one non-zero-size field?

It's the only case that comes to mind, but I'd be surprised if other use cases don't exist.

> [@hanna-kruppe](#):
>
> - the `MaybeUninit` arguments are not used at all, neither by callers nor callees, they are only added to make the actually-used arguments end up in more desirable registers

Correct.

> [@hanna-kruppe](#):
>
> - this effect could be achieved more naturally with more direct control over the ABI of `__objrs_msg_send_print`

Yes, but there is no mechanism in Rust to have more control over this. Inline `asm!` might work inline `asm!` is never going to get stabilized, and even if it did I'm not confident I could make it work reliably (because the method needs to match the C calling convention so it can `jmp` directly to `objc_msgSend`, which means I'd need to match the C calling convention, which would require knowing the ABI of all the arguments, none of which is currently possible).

> [@hanna-kruppe](#):
>
> - alternatively it could also be achieved with a different (non-union) type as well

I haven't been able to find any (non-union) type that works. Everything has to be initialized before it is used in Rust. [ZSTs don't work because they aren't actually passed as arguments](https://rust.godbolt.org/z/5jXfbK). The only way to have an uninitialized value is to use `MaybeUninit` (or an equivalent union).

> [@hanna-kruppe](#):
>
> - Though the current codegen is not guaranteed, if it ever changes then objrs will just take a performance hit, not start hitting UB
> - (Worth noting that the codegen guarantee of `repr(transparent)` is about _ABI compatibility_ , not about the actual thing you care about, good register allocation.)

That's totally true, and there is no guarantee that the code generator will generate the optimal assembly for this. But having `T` and `MaybeUninit<T>` match at the ABI level makes this kind of optmization much more likely to succeed (otherwise, if `T`'s ABI is to be passed in a register but `MaybeUninit<T>`'s ABI is to be passed on the stack (with a pointer to the memory location), then this attempted optimization will not just fail but actually backfire).

> [@hanna-kruppe](#):
>
> If that’s correct, then I feel like this example is not just “not the best example” but at best tangentially related to `repr(transparent)` , and I’d really like to see an example for which `repr(transparent)` is actually important.

I agree. I have some other FFI-related ideas I need to explore to see if they're better justifications for this RFC, but I'd be happy to hear any other use cases others might have.

> [@comex](#):
>
> Don’t want to bikeshed too much, but an alternative would be to allow `#[repr(transparent)]` on any union, and give it the representation of its first member or something (which would have to be large enough).

That's a really interesting idea. I'm personally hesitant to reuse `#[repr(transparent)]` for this. I'd prefer something new like `#[repr(type = DesiredRepresentationType)]` (where, for your examples, `DesiredRepresentationType` would be `u64` for `Foo` and `f64` for `Bar`), proposed in a separate RFC.

---

<div class="post-metadata">

### Author: ![gnzlbg](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/gnzlbg/32/3858_2.png) [@gnzlbg](https://internals.rust-lang.org/u/gnzlbg)
#### Post date: [February 15, 2019, 7:45am UTC](https://internals.rust-lang.org/t/pre-rfc-transparent-unions/9441/9 "2019-02-15T07:45:49Z")

</div>

Why shouldn’t unions with one non-zero sized fill not always be automatically “transparent” (have the same ABI as the non-zero field)? EDIT: too many negatives, the question is why do we need to add `repr(transparent)` at all? We should do this automatically.

We had this problem with `MaybeUninit` not propagating the ABI of SIMD types in libcore, and IIRC we fixed it by making unions with one non-zero-sized field always propagate the ABI of the type. What does `repr(transparent)` add on top of that?

EDIT: If you want the non-transparent behavior, you can do `struct Aggregate(MaybeUninit<f32x4>);` .

---

<div class="post-metadata">

### Author: ![notriddle](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/notriddle/32/14082_2.png) [@notriddle](https://internals.rust-lang.org/u/notriddle)
#### Post date: [February 15, 2019, 1:22pm UTC](https://internals.rust-lang.org/t/pre-rfc-transparent-unions/9441/10 "2019-02-15T13:22:29Z")

</div>

What about types like `[0; f64]` with zero size but non-zero alignment?

---

<div class="post-metadata">

### Author: ![scottmcm](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/scottmcm/32/2355_2.png) [@scottmcm](https://internals.rust-lang.org/u/scottmcm)
#### Post date: [February 15, 2019, 6:06pm UTC](https://internals.rust-lang.org/t/pre-rfc-transparent-unions/9441/11 "2019-02-15T18:06:14Z")

</div>

> [@gnzlbg](#):
>
> the question is why do we need to add `repr(transparent)` at all? We should do this automatically

Same answer as for structs, I assume: repr(rust) is free to do whatever optimizations the compiler wants, but if you want to rely on it you should say on what you're relying.

---

<div class="post-metadata">

### Author: ![gnzlbg](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/gnzlbg/32/3858_2.png) [@gnzlbg](https://internals.rust-lang.org/u/gnzlbg)
#### Post date: [February 20, 2019, 10:06am UTC](https://internals.rust-lang.org/t/pre-rfc-transparent-unions/9441/12 "2019-02-20T10:06:43Z")

</div>

> [@scottmcm](#):
>
> Same answer as for structs, I assume: repr(rust) is free to do whatever optimizations the compiler wants, but if you want to rely on it you should say on what you’re relying.

Unless we guarantee that `repr(rust)` does this, in which case, adding `repr(transparent)` becomes unnecessary.

So my question is still, why should we add `repr(transparent)` instead of guaranteeing that `repr(rust)` does this? Which optimizations are enabled by not guaranteeing "`repr(transparent)"-like behavior for `repr(rust)`?

In particular, `libstd` is already relying on `repr(rust)` doing this. We can change that if we have to add `repr(transparent)` though, but we already do have some experience with making `repr(rust)` guarantee this. What have we learned from this experience? Have we learned of any optimizations that have become or will be impossible because of this? Can anybody think of any? How important are they? Etc.

* * *

I'm not sure what value does adding a second solution to this problem add. If the value is "future-proofing", then an RFC should make a point of what are we "future-proofing" for, e.g., which optimizations will this enable for `repr(rust)` unions. If the answer is "none", then IMO the case for adding this is very weak, it is just one extra thing people will need to remember when using unions that we could do automatically instead.

Maybe even worse than that, we will be doing this automatically for them anyways, but if they silently rely on that they would be relying on unspecified behavior while this is something that we could just easily specify. At that point we might want to consider adding a lint that detects these types of unions and tells users to add `repr(transparent)` to avoid relying on unspecified behavior.

---

<div class="post-metadata">

### Author: ![RalfJung](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/ralfjung/32/2415_2.png) [@RalfJung](https://internals.rust-lang.org/u/RalfJung)
#### Post date: [February 20, 2019, 2:43pm UTC](https://internals.rust-lang.org/t/pre-rfc-transparent-unions/9441/13 "2019-02-20T14:43:44Z")

</div>

> [@gnzlbg](#):
>
> So my question is still, why should we add `repr(transparent)` instead of guaranteeing that `repr(rust)` does this? Which optimizations are enabled by not guaranteeing " `repr(transparent)"-like behavior for` repr(rust)`?

`repr(transparent)` is still useful because it will show an error when you _intend_ something to be transparent, but accidentally violated one of the constraints (like having only one non-ZST field).

---

<div class="post-metadata">

### Author: ![gnzlbg](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/gnzlbg/32/3858_2.png) [@gnzlbg](https://internals.rust-lang.org/u/gnzlbg)
#### Post date: [February 24, 2019, 1:12pm UTC](https://internals.rust-lang.org/t/pre-rfc-transparent-unions/9441/14 "2019-02-24T13:12:44Z")

</div>

> [@RalfJung](#):
>
> `repr(transparent)` is still useful because it will show an error when you _intend_ something to be transparent, but accidentally violated one of the constraints (like having only one non-ZST field).

That should be its main selling point.

---

<div class="post-metadata">

### Author: ![mjbshaw](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/mjbshaw/32/5103_2.png) [@mjbshaw](https://internals.rust-lang.org/u/mjbshaw)
#### Post date: [February 25, 2019, 5:39am UTC](https://internals.rust-lang.org/t/pre-rfc-transparent-unions/9441/15 "2019-02-25T05:39:24Z")

</div>

Thanks everyone for the input. [I have revised the text and opened a merge request to rust-lang/rfcs](https://github.com/rust-lang/rfcs/pull/2645).

---

<div class="post-metadata">

### Author: ![system](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/system/32/14092_2.png) [@system](https://internals.rust-lang.org/u/system)
#### Post date: [May 26, 2019, 5:39am UTC](https://internals.rust-lang.org/t/pre-rfc-transparent-unions/9441/16 "2019-05-26T05:39:24Z")

</div>

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.
