Skip to content

Commit fcfb77d

Browse files
authored
Merge pull request #161 from gnzlbg/union_single_field
Guarantee the layout of some unions having a single non-zero-sized field
2 parents e7395e0 + 6b544f3 commit fcfb77d

File tree

2 files changed

+94
-13
lines changed

2 files changed

+94
-13
lines changed

reference/src/glossary.md

+1
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,7 @@ Accordingly, we say that a library (or an individual function) is *sound* if it
171171
Conversely, the library/function is *unsound* if safe code *can* cause Undefined Behavior.
172172

173173
#### Layout
174+
[layout]: #layout
174175

175176
The *layout* of a type defines its size and alignment as well as the offsets of its subobjects (e.g. fields of structs/unions/enum/... or elements of arrays).
176177
Moreover, the layout of a type records its *function call ABI* (or just *ABI* for short): how the type is passed *by value* across a function boundary.

reference/src/layout/unions.md

+93-13
Original file line numberDiff line numberDiff line change
@@ -6,29 +6,107 @@ not to change until an RFC ratifies them.
66

77
[#13]: https://github.com/rust-rfcs/unsafe-code-guidelines/issues/13
88

9-
The only degree of freedom the compiler has when computing the layout of a union
10-
like
9+
### Layout of individual union fields
1110

12-
```rust,ignore
13-
union U { f1: T1, f2: T2 }
11+
A union consists of several variants, one for each field. All variants have the
12+
same size and start at the same memory address, such that in memory the variants
13+
overlap. This can be visualized as follows:
14+
15+
```text
16+
[ <--> [field0_ty] <----> ]
17+
[ <----> [field1_ty] <--> ]
18+
[ <---> [field2_ty] <---> ]
1419
```
20+
**Figure 1** (union-field layout): Each row in the picture shows the layout of
21+
the union for each of its variants. The `<-...->` and `[ ... ]` denote the
22+
differently-sized gaps and fields, respectively.
23+
24+
The individual fields (`[field{i}_ty_]`) are blocks of fixed size determined by
25+
the field's [layout]. Since we allow creating references to union fields
26+
(`&u.i`), the only degrees of freedom the compiler has when computing the layout
27+
of a union are the size of the union, which can be larger than the size of its
28+
largest field, and the offset of each union field within its variant. How these
29+
are picked depends on certain constraints like, for example, the alignment
30+
requirements of the fields, the `#[repr]` attribute of the `union`, etc.
31+
32+
[padding]: ../glossary.md#padding
33+
[layout]: ../glossary.md#layout
34+
35+
### Unions with default layout ("`repr(Rust)`")
1536

16-
is to determine the offset of the fields. The layout of these fields themselves
17-
is already entirely determined by their types, and since we intend to allow
18-
creating references to fields (`&u.f1`), unions do not have any wiggle-room
19-
there.
37+
Except for the guarantees provided below for some specific cases, the default
38+
layout of Rust unions is, _in general_, **unspecified**.
2039

21-
### Default layout ("repr rust")
40+
That is, there are no _general_ guarantees about the offset of the fields,
41+
whether all fields have the same offset, what the call ABI of the union is, etc.
2242

23-
**The default layout of unions is not specified.** As of this writing, we want
24-
to keep the option of using non-zero offsets open for the future; whether this
25-
is useful depends on what exactly the compiler-assumed invariants about union
26-
contents are.
43+
<details><summary><b>Rationale</b></summary>
44+
45+
As of this writing, we want to keep the option of using non-zero offsets open
46+
for the future; whether this is useful depends on what exactly the
47+
compiler-assumed invariants about union contents are. This might become clearer
48+
after the [validity of unions][#73] is settled.
2749

2850
Even if the offsets happen to be all 0, there might still be differences in the
2951
function call ABI. If you need to pass unions by-value across an FFI boundary,
3052
you have to use `#[repr(C)]`.
3153

54+
[#73]: https://github.com/rust-lang/unsafe-code-guidelines/issues/73
55+
56+
</details>
57+
58+
#### Layout of unions with a single non-zero-sized field
59+
60+
The layout of unions with a single non-[1-ZST]-field" is the same as the
61+
layout of that field if it has no [padding] bytes.
62+
63+
For example, here:
64+
65+
```rust
66+
# use std::mem::{size_of, align_of};
67+
# #[derive(Copy, Clone)]
68+
#[repr(transparent)]
69+
struct SomeStruct(i32);
70+
# #[derive(Copy, Clone)]
71+
struct Zst;
72+
union U0 {
73+
f0: SomeStruct,
74+
f1: Zst,
75+
}
76+
# fn main() {
77+
# assert_eq!(size_of::<U0>(), size_of::<SomeStruct>());
78+
# assert_eq!(align_of::<U0>(), align_of::<SomeStruct>());
79+
# }
80+
```
81+
82+
the union `U0` has the same layout as `SomeStruct`, because `SomeStruct` has no
83+
padding bits - it is equivalent to an `i32` due to `repr(transparent)` - and
84+
because `Zst` is a [1-ZST].
85+
86+
On the other hand, here:
87+
88+
```rust
89+
# use std::mem::{size_of, align_of};
90+
# #[derive(Copy, Clone)]
91+
struct SomeOtherStruct(i32);
92+
# #[derive(Copy, Clone)]
93+
#[repr(align(16))] struct Zst2;
94+
union U1 {
95+
f0: SomeOtherStruct,
96+
f1: Zst2,
97+
}
98+
# fn main() {
99+
# assert_eq!(size_of::<U1>(), align_of::<Zst2>());
100+
# assert_eq!(align_of::<U1>(), align_of::<Zst2>());
101+
assert_eq!(align_of::<Zst2>(), 16);
102+
# }
103+
```
104+
105+
the layout of `U1` is **unspecified** because:
106+
107+
* `Zst2` is not a [1-ZST], and
108+
* `SomeOtherStruct` has an unspecified layout and could contain padding bytes.
109+
32110
### C-compatible layout ("repr C")
33111

34112
The layout of `repr(C)` unions follows the C layout scheme. Per sections
@@ -93,3 +171,5 @@ with no fields. When such types are used as an union field in C++, a "naive"
93171
translation of that code into Rust will not produce a compatible result. Refer
94172
to the [struct chapter](structs-and-tuples.md#c-compatible-layout-repr-c) for
95173
further details.
174+
175+
[1-ZST]: ../glossary.md#zero-sized-type--zst

0 commit comments

Comments
 (0)