Skip to content

Commit 8c92dec

Browse files
committed
Add some initial text about transparent enums
1 parent 541934e commit 8c92dec

File tree

1 file changed

+129
-20
lines changed

1 file changed

+129
-20
lines changed

text/0000-transparent-unions.md

Lines changed: 129 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,95 @@
1-
- Feature Name: transparent_unions
1+
- Feature Name: transparent_enunions
22
- Start Date: 2019-02-13
33
- RFC PR:
44
- Rust Issue:
55

66
# Summary
77
[summary]: #summary
88

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

1111
# Motivation
1212
[motivation]: #motivation
1313

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.26.1/src/core/mem.rs.html#950), `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.
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.
1515

16-
Making types like these `#[repr(transparent)]` would be useful in certain cases. For example, making a `union Wrapper<T>` transparent:
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:
1719

1820
- Clearly expresses the intent of the developer.
19-
- Protects against accidental violations of that intent (e.g., adding a new non-ZST field to a transparent union will result in a compiler error).
20-
- Makes a clear API guarantee that a `Wrapper<T>` can be transmuted to a `T`.
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.
2123

22-
Transparent `union`s are a nice complement to transparent `struct`s, and this RFC is a step towards rounding out the `#[repr(transparent)]` feature.
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.
2325

2426
# Guide-level explanation
2527
[guide-level-explanation]: #guide-level-explanation
2628

27-
A `union` may be `#[repr(transparent)]` in exactly the same conditions in which a struct may be `#[repr(transparent)]`. Some concrete illustrations follow.
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.
2830

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

3133
```rust
34+
// This union has the same representation as `f32`.
35+
#[repr(transparent)]
36+
union SingleFieldUnion {
37+
field: f32,
38+
}
39+
3240
// This union has the same representation as `usize`.
3341
#[repr(transparent)]
34-
union CustomUnion {
42+
union MultiFieldUnion {
3543
field: usize,
3644
nothing: (),
3745
}
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+
}
3858
```
3959

40-
For consistency with transparent `struct`s, a `union` must have exactly one non-zero-sized field. If all fields are zero-sized, the `union` must not be `#[repr(transparent)]`:
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)]`:
4161

4262
```rust
4363
// This (non-transparent) union is already valid in stable Rust:
44-
pub union Good {
64+
pub union GoodUnion {
4565
pub nothing: (),
4666
}
4767

68+
// This (non-transparent) enum is already valid in stable Rust:
69+
pub enum GoodEnum {
70+
Nothing,
71+
}
72+
4873
// Error: transparent union needs exactly one non-zero-sized field, but has 0
4974
#[repr(transparent)]
50-
pub union Bad {
75+
pub union BadUnion {
5176
pub nothing: (),
5277
}
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+
}
5390
```
5491

55-
The one exception is if the `union` is generic over `T` and has a field of type `T`, it may be `#[repr(transparent)]` even if `T` is a zero-sized type:
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:
5693

5794
```rust
5895
// This union has the same representation as `T`.
@@ -62,29 +99,103 @@ pub union GenericUnion<T: Copy> { // Unions with non-`Copy` fields are unstable.
6299
pub nothing: (),
63100
}
64101

102+
// This enum has the same representation as `T`.
103+
#[repr(transparent)]
104+
pub enum GenericEnum<T> {
105+
Variant(T, ()),
106+
}
107+
65108
// This is okay even though `()` is a zero-sized type.
66109
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+
}
67127
```
68128

69129
# Reference-level explanation
70130
[reference-level-explanation]: #reference-level-explanation
71131

72-
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).
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.
73137

74138
# Drawbacks
75139
[drawbacks]: #drawbacks
76140

77-
- `#[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`.
141+
`#[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.
78142

79143
# Rationale and alternatives
80144
[alternatives]: #alternatives
81145

82-
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.
146+
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:
147+
148+
```rust
149+
#[repr(C)]
150+
struct Context {
151+
// Imagine there a few fields here, defined by an external C library.
152+
}
153+
154+
extern "C" fn log_event(message: core::ptr::NonNull<libc::c_char>,
155+
context: core::mem::MaybeUninit<Context>) {
156+
// Log the message here, but ignore the context since we don't need it.
157+
}
158+
159+
fn main() {
160+
extern "C" {
161+
fn set_log_handler(handler: extern "C" fn(core::ptr::NonNull<libc::c_char>,
162+
Context));
163+
}
164+
165+
// Set the log handler so the external C library can call log_event.
166+
unsafe {
167+
// Transmuting is safe since MaybeUninit<Context> and Context
168+
// have the same ABI.
169+
set_log_handler(core::mem::transmute(log_event as *const ()));
170+
}
171+
172+
// We can call it too. And since we don't care about the context and
173+
// we're using MaybeUninit, we don't have to pay any extra cost for
174+
// initializing something that's unused.
175+
log_event(core::ptr::NonNull::new(b"Hello, world!\x00".as_ptr() as *mut _).unwrap(),
176+
core::mem::MaybeUninit::uninitialized());
177+
}
178+
```
179+
180+
It is also useful for consuming pointers to uninitialized memory:
181+
182+
```rust
183+
#[repr(C)]
184+
struct Cryptor {
185+
// Imagine there a few fields here, defined by an external C library.
186+
}
187+
188+
// This method may be called from C (or Rust!), and matches the C
189+
// function signature: bool(Cryptor *cryptor)
190+
pub extern "C" fn init_cryptor(cryptor: &mut core::mem::MaybeUninit<Cryptor>) -> bool {
191+
// Initialize the cryptor and return whether we succeeded
192+
}
193+
```
83194

84195
# Prior art
85196
[prior-art]: #prior-art
86197

87-
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:
198+
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:
88199

89200
[nagisa_1]: https://github.com/rust-lang/rfcs/pull/1758#discussion_r80436621
90201
> + **[nagisa][nagisa_1]:** "Why not univariant unions and enums?"
@@ -101,7 +212,7 @@ See [the discussion on RFC #1758](https://github.com/rust-lang/rfcs/pull/1758) (
101212
[pnkfelix_1]: https://github.com/rust-lang/rfcs/pull/1758#issuecomment-290757356
102213
> + **[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.)"
103214
104-
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, so there should be no problems with applying those same answers to `union`.
215+
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`.
105216

106217
# Unresolved questions
107218
[unresolved]: #unresolved-questions
@@ -111,6 +222,4 @@ None (yet).
111222
# Future possibilities
112223
[future-possibilities]: #future-possibilities
113224

114-
Univariant `enum`s are ommitted from this RFC in an effort to keep the scope small. A future RFC could explore `#[repr(transparent)]` on a univariant `enum`.
115-
116225
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)