Skip to content

Commit 2e1858f

Browse files
authored
Merge pull request #2645 from mjbshaw/transparent_unions
Transparent Unions and Enums
2 parents 3c9ddf2 + bf09395 commit 2e1858f

File tree

1 file changed

+229
-0
lines changed

1 file changed

+229
-0
lines changed

text/2645-transparent-unions.md

+229
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
- Feature Name: `transparent_enunions`
2+
- Start Date: 2019-02-13
3+
- RFC PR: [rust-lang/rfcs#2645](https://github.com/rust-lang/rfcs/pull/2645)
4+
- Rust Issue: [rust-lang/rust#60405](https://github.com/rust-lang/rust/issues/60405)
5+
6+
# Summary
7+
[summary]: #summary
8+
9+
Allow `#[repr(transparent)]` on `union`s and univariant `enum`s that have exactly one non-zero-sized field (just like `struct`s).
10+
11+
# Motivation
12+
[motivation]: #motivation
13+
14+
Some `union` types are thin newtype-style wrappers around another type, like `MaybeUninit<T>` (and [once upon a time](https://doc.rust-lang.org/1.28.0/src/core/mem.rs.html#955), `ManuallyDrop<T>`). This type is intended to be used in the same places as `T`, but without being `#[repr(transparent)]` the actual compatibility between it and `T` is left unspecified.
15+
16+
Likewise, some `enum` types only have a single variant, and are similarly thin wrappers around another type.
17+
18+
Making types like these `#[repr(transparent)]` would be useful in certain cases. For example, making the type `Wrapper<T>` (which is a `union` or univariant `enum` with a single field of type `T`) transparent:
19+
20+
- Clearly expresses the intent of the developer.
21+
- Protects against accidental violations of that intent (e.g., adding a new variant or non-ZST field will result in a compiler error).
22+
- Makes a clear API guarantee that a `Wrapper<T>` can be transmuted to a `T` or substituted for a `T` in an FFI function's signature (though users must be careful to not pass uninitialized values through FFI to code where uninitialized values are undefined behavior (like C and C++)).
23+
24+
Transparent `union`s and univariant `enum`s are a nice complement to transparent `struct`s, and this RFC rounds out the `#[repr(transparent)]` feature.
25+
26+
# Guide-level explanation
27+
[guide-level-explanation]: #guide-level-explanation
28+
29+
A `union` may be `#[repr(transparent)]` in exactly the same conditions in which a `struct` may be `#[repr(transparent)]`. An `enum` may be `#[repr(transparent)]` if it has exactly one variant, and that variant matches the same conditions which `struct` requires for transparency. Some concrete illustrations follow.
30+
31+
A union may be `#[repr(transparent)]` if it has exactly one non-zero-sized field:
32+
33+
```rust
34+
// This union has the same representation as `f32`.
35+
#[repr(transparent)]
36+
union SingleFieldUnion {
37+
field: f32,
38+
}
39+
40+
// This union has the same representation as `usize`.
41+
#[repr(transparent)]
42+
union MultiFieldUnion {
43+
field: usize,
44+
nothing: (),
45+
}
46+
47+
// This enum has the same representation as `f32`.
48+
#[repr(transparent)]
49+
enum SingleFieldEnum {
50+
Variant(f32)
51+
}
52+
53+
// This enum has the same representation as `usize`.
54+
#[repr(transparent)]
55+
enum MultiFieldEnum {
56+
Variant { field: usize, nothing: () },
57+
}
58+
```
59+
60+
For consistency with transparent `struct`s, `union`s and `enum`s must have exactly one non-zero-sized field. If all fields are zero-sized, the `union` or `enum` must not be `#[repr(transparent)]`:
61+
62+
```rust
63+
// This (non-transparent) union is already valid in stable Rust:
64+
pub union GoodUnion {
65+
pub nothing: (),
66+
}
67+
68+
// This (non-transparent) enum is already valid in stable Rust:
69+
pub enum GoodEnum {
70+
Nothing,
71+
}
72+
73+
// Error: transparent union needs exactly one non-zero-sized field, but has 0
74+
#[repr(transparent)]
75+
pub union BadUnion {
76+
pub nothing: (),
77+
}
78+
79+
// Error: transparent enum needs exactly one non-zero-sized field, but has 0
80+
#[repr(transparent)]
81+
pub enum BadEnum {
82+
Nothing(()),
83+
}
84+
85+
// Error: transparent enum needs exactly one non-zero-sized field, but has 0
86+
#[repr(transparent)]
87+
pub enum BadEmptyEnum {
88+
Nothing,
89+
}
90+
```
91+
92+
The one exception is if the `union` or `enum` is generic over `T` and has a field of type `T`, it may be `#[repr(transparent)]` even if `T` is a zero-sized type:
93+
94+
```rust
95+
// This union has the same representation as `T`.
96+
#[repr(transparent)]
97+
pub union GenericUnion<T: Copy> { // Unions with non-`Copy` fields are unstable.
98+
pub field: T,
99+
pub nothing: (),
100+
}
101+
102+
// This enum has the same representation as `T`.
103+
#[repr(transparent)]
104+
pub enum GenericEnum<T> {
105+
Variant(T, ()),
106+
}
107+
108+
// This is okay even though `()` is a zero-sized type.
109+
pub const THIS_IS_OKAY: GenericUnion<()> = GenericUnion { field: () };
110+
pub const THIS_IS_OKAY_TOO: GenericEnum<()> = GenericEnum::Variant((), ());
111+
```
112+
113+
Transparent `enum`s have the addtional restriction that they require exactly one variant:
114+
115+
```rust
116+
// Error: transparent enum needs exactly one variant, but has 0
117+
#[repr(transparent)]
118+
pub enum TooFewVariants {
119+
}
120+
121+
// Error: transparent enum needs exactly one variant, but has 2
122+
#[repr(transparent)]
123+
pub enum TooManyVariants {
124+
First(usize),
125+
Second(usize),
126+
}
127+
```
128+
129+
# Reference-level explanation
130+
[reference-level-explanation]: #reference-level-explanation
131+
132+
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). An `enum` of type `E` may be `#[repr(transparent)]` if it has exactly one variant, and that variant follows all the rules and logic controlling whether a `struct` of type `S` may be `#[repr(transparent)]` (assuming `E` and `S` have the same generic parameters, and `E`'s variant and `S` have the same and fields).
133+
134+
Like transarent `struct`s, a transparent `union` of type `U` and transparent `enum` of type `E` have the same layout, size, and ABI as their single non-ZST field. If they are generic over a type `T`, and all their fields are ZSTs except for exactly one field of type `T`, then they have the same layout and ABI as `T` (even if `T` is a ZST when monomorphized).
135+
136+
Like transparent `struct`s, transparent `union`s and `enum`s are FFI-safe if and only if their underlying representation type is also FFI-safe.
137+
138+
A `union` may not be eligible for the same nonnull-style optimizations that a `struct` or `enum` (with the same fields) are eligible for. Adding `#[repr(transparent)]` to `union` does not change this. To give a more concrete example, it is unspecified whether `size_of::<T>()` is equal to `size_of::<Option<T>>()`, where `T` is a `union` (regardless of whether it is transparent). The Rust compiler is free to perform this optimization if possible, but is not required to, and different compiler versions may differ in their application of these optimizations.
139+
140+
# Drawbacks
141+
[drawbacks]: #drawbacks
142+
143+
`#[repr(transparent)]` on a `union` or `enum` 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 type in a cargo-cult fashion.
144+
145+
# Rationale and alternatives
146+
[alternatives]: #alternatives
147+
148+
It would be nice to make `MaybeUninit<T>` `#[repr(transparent)]`. This type is a `union`, and thus this RFC is required to allow making it transparent. One example in which a transparent representation would be useful is for unused parameters in an FFI-function:
149+
150+
```rust
151+
#[repr(C)]
152+
struct Context {
153+
// Imagine there a few fields here, defined by an external C library.
154+
}
155+
156+
extern "C" fn log_event(message: core::ptr::NonNull<libc::c_char>,
157+
context: core::mem::MaybeUninit<Context>) {
158+
// Log the message here, but ignore the context since we don't need it.
159+
}
160+
161+
fn main() {
162+
extern "C" {
163+
fn set_log_handler(handler: extern "C" fn(core::ptr::NonNull<libc::c_char>,
164+
Context));
165+
}
166+
167+
// Set the log handler so the external C library can call log_event.
168+
unsafe {
169+
// Transmuting is safe since MaybeUninit<Context> and Context
170+
// have the same ABI.
171+
set_log_handler(core::mem::transmute(log_event as *const ()));
172+
}
173+
174+
// We can call it too. And since we don't care about the context and
175+
// we're using MaybeUninit, we don't have to pay any extra cost for
176+
// initializing something that's unused.
177+
log_event(core::ptr::NonNull::new(b"Hello, world!\x00".as_ptr() as *mut _).unwrap(),
178+
core::mem::MaybeUninit::uninitialized());
179+
}
180+
```
181+
182+
It is also useful for consuming pointers to uninitialized memory:
183+
184+
```rust
185+
#[repr(C)]
186+
struct Cryptor {
187+
// Imagine there a few fields here, defined by an external C library.
188+
}
189+
190+
// This function may be called from C (or Rust!), and matches the C
191+
// function signature: bool(Cryptor *cryptor)
192+
pub extern "C" fn init_cryptor(cryptor: &mut core::mem::MaybeUninit<Cryptor>) -> bool {
193+
// Initialize the cryptor and return whether we succeeded
194+
}
195+
```
196+
197+
# Prior art
198+
[prior-art]: #prior-art
199+
200+
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` or `enum`. A summary of the discussion:
201+
202+
[nagisa_1]: https://github.com/rust-lang/rfcs/pull/1758#discussion_r80436621
203+
> + **[nagisa][nagisa_1]:** "Why not univariant unions and enums?"
204+
> + **nox:** "I tried to be conservative for now given I don't have a use case for univariant unions and enums in FFI context."
205+
206+
[eddyb_1]: https://github.com/rust-lang/rfcs/pull/1758#issuecomment-254872520
207+
> + **[eddyb][eddyb_1]:** "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"
208+
> + **retep998:** "So we'd need to be able to specify `#[repr(transparent)]` on unions?"
209+
> + **eddyb:** "That's the only way to be sure AFAICT, yes."
210+
211+
[joshtriplett_1]: https://github.com/rust-lang/rfcs/pull/1758#issuecomment-274670231
212+
> + **[joshtriplett][joshtriplett_1]:** "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."
213+
214+
[pnkfelix_1]: https://github.com/rust-lang/rfcs/pull/1758#issuecomment-290757356
215+
> + **[pnkfelix][pnkfelix_1]:** "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.)"
216+
217+
In summary, many of the questions regarding `#[repr(transparent)]` on a `union` or `enum` were the same as applying it to a multi-field `struct`. These questions have since been answered, so there should be no problems with applying those same answers to `union` univariant `enum`.
218+
219+
# Unresolved questions
220+
[unresolved]: #unresolved-questions
221+
222+
The role of `#[repr(transparent)]` in nonnull-style optimizations is not entirely clear. Specifically, it is unclear whether the user can rely on these optimizations to be performed when they make a type transparent. [Transparent `union`s somewhat complicate the matter](https://github.com/rust-lang/rfcs/pull/2645#issuecomment-470699497). General concensus seems to be that the compiler is free to decide where and when to perform nonnull-style optimizations on `union`s (regardless of whether or not the `union` is transaprent), and no guarantees are made to the user about when and if those optimizations will be applied. It is still an open question exactly what guarantees (if any) Rust makes about transparent `struct`s (and `enum`s) and nonnull-style optimizations.
223+
224+
This RFC doesn't propose any changes to transparent `struct`s, and so does not strictly depend on this question being resolved. But since this RFC is attempting to round out the `#[repr(transparent)]` feature, it seems reasonable to dedicate some time to attempting to round out the guarantees about `#[repr(transparent)]` on `struct`s.
225+
226+
# Future possibilities
227+
[future-possibilities]: #future-possibilities
228+
229+
If a `union` has multiple non-ZST fields, a future RFC could propose a way to choose the representation of that `union` ([example](https://internals.rust-lang.org/t/pre-rfc-transparent-unions/9441/6)).

0 commit comments

Comments
 (0)