- 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 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:
#[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:
__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:
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:
// 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):
// 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 aunion
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 aunion
.
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 (which introduced #[repr(transparent)]
) for some discussion on applying the attribute to a union
. A summary of the discussion:
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 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 asT
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 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 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 leaveunion
/enum
to a follow-on RFC later. (Much the same with a hypotheticalnewtype
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
.