Skip to content

Commit b887317

Browse files
committed
Auto merge of #47158 - rkruppe:repr-transparent, r=eddyb
Implement repr(transparent) r? @eddyb for the functional changes. The bulk of the PR is error messages and docs, might be good to have a doc person look over those. cc #43036 cc @nox
2 parents bc072ed + 2be697b commit b887317

21 files changed

+881
-12
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
# `repr_transparent`
2+
3+
The tracking issue for this feature is: [#43036]
4+
5+
[#43036]: https://github.com/rust-lang/rust/issues/43036
6+
7+
------------------------
8+
9+
This feature enables the `repr(transparent)` attribute on structs, which enables
10+
the use of newtypes without the usual ABI implications of wrapping the value in
11+
a struct.
12+
13+
## Background
14+
15+
It's sometimes useful to add additional type safety by introducing *newtypes*.
16+
For example, code that handles numeric quantities in different units such as
17+
millimeters, centimeters, grams, kilograms, etc. may want to use the type system
18+
to rule out mistakes such as adding millimeters to grams:
19+
20+
```rust
21+
use std::ops::Add;
22+
23+
struct Millimeters(f64);
24+
struct Grams(f64);
25+
26+
impl Add<Millimeters> for Millimeters {
27+
type Output = Millimeters;
28+
29+
fn add(self, other: Millimeters) -> Millimeters {
30+
Millimeters(self.0 + other.0)
31+
}
32+
}
33+
34+
// Likewise: impl Add<Grams> for Grams {}
35+
```
36+
37+
Other uses of newtypes include using `PhantomData` to add lifetimes to raw
38+
pointers or to implement the "phantom types" pattern. See the [PhantomData]
39+
documentation and [the Nomicon][nomicon-phantom] for more details.
40+
41+
The added type safety is especially useful when interacting with C or other
42+
languages. However, in those cases we need to ensure the newtypes we add do not
43+
introduce incompatibilities with the C ABI.
44+
45+
## Newtypes in FFI
46+
47+
Luckily, `repr(C)` newtypes are laid out just like the type they wrap on all
48+
platforms which Rust currently supports, and likely on many more. For example,
49+
consider this C declaration:
50+
51+
```C
52+
struct Object {
53+
double weight; //< in grams
54+
double height; //< in millimeters
55+
// ...
56+
}
57+
58+
void frobnicate(struct Object *);
59+
```
60+
61+
While using this C code from Rust, we could add `repr(C)` to the `Grams` and
62+
`Millimeters` newtypes introduced above and use them to add some type safety
63+
while staying compatible with the memory layout of `Object`:
64+
65+
```rust,no_run
66+
#[repr(C)]
67+
struct Grams(f64);
68+
69+
#[repr(C)]
70+
struct Millimeters(f64);
71+
72+
#[repr(C)]
73+
struct Object {
74+
weight: Grams,
75+
height: Millimeters,
76+
// ...
77+
}
78+
79+
extern {
80+
fn frobnicate(_: *mut Object);
81+
}
82+
```
83+
84+
This works even when adding some `PhantomData` fields, because they are
85+
zero-sized and therefore don't have to affect the memory layout.
86+
87+
However, there's more to the ABI than just memory layout: there's also the
88+
question of how function call arguments and return values are passed. Many
89+
common ABI treat a struct containing a single field differently from that field
90+
itself, at least when the field is a scalar (e.g., integer or float or pointer).
91+
92+
To continue the above example, suppose the C library also exposes a function
93+
like this:
94+
95+
```C
96+
double calculate_weight(double height);
97+
```
98+
99+
Using our newtypes on the Rust side like this will cause an ABI mismatch on many
100+
platforms:
101+
102+
```rust,ignore
103+
extern {
104+
fn calculate_weight(height: Millimeters) -> Grams;
105+
}
106+
```
107+
108+
For example, on x86_64 Linux, Rust will pass the argument in an integer
109+
register, while the C function expects the argument to be in a floating-point
110+
register. Likewise, the C function will return the result in a floating-point
111+
register while Rust will expect it in an integer register.
112+
113+
Note that this problem is not specific to floats: To give another example,
114+
32-bit x86 linux will pass and return `struct Foo(i32);` on the stack while
115+
`i32` is placed in registers.
116+
117+
## Enter `repr(transparent)`
118+
119+
So while `repr(C)` happens to do the right thing with respect to memory layout,
120+
it's not quite the right tool for newtypes in FFI. Instead of declaring a C
121+
struct, we need to communicate to the Rust compiler that our newtype is just for
122+
type safety on the Rust side. This is what `repr(transparent)` does.
123+
124+
The attribute can be applied to a newtype-like structs that contains a single
125+
field. It indicates that the newtype should be represented exactly like that
126+
field's type, i.e., the newtype should be ignored for ABI purpopses: not only is
127+
it laid out the same in memory, it is also passed identically in function calls.
128+
129+
In the above example, the ABI mismatches can be prevented by making the newtypes
130+
`Grams` and `Millimeters` transparent like this:
131+
132+
```rust
133+
#![feature(repr_transparent)]
134+
135+
#[repr(transparent)]
136+
struct Grams(f64);
137+
138+
#[repr(transparent)]
139+
struct Millimeters(f64);
140+
```
141+
142+
In addition to that single field, any number of zero-sized fields are permitted,
143+
including but not limited to `PhantomData`:
144+
145+
```rust
146+
#![feature(repr_transparent)]
147+
148+
use std::marker::PhantomData;
149+
150+
struct Foo { /* ... */ }
151+
152+
#[repr(transparent)]
153+
struct FooPtrWithLifetime<'a>(*const Foo, PhantomData<&'a Foo>);
154+
155+
#[repr(transparent)]
156+
struct NumberWithUnit<T, U>(T, PhantomData<U>);
157+
158+
struct CustomZst;
159+
160+
#[repr(transparent)]
161+
struct PtrWithCustomZst<'a> {
162+
ptr: FooPtrWithLifetime<'a>,
163+
some_marker: CustomZst,
164+
}
165+
```
166+
167+
Transparent structs can be nested: `PtrWithCustomZst` is also represented
168+
exactly like `*const Foo`.
169+
170+
Because `repr(transparent)` delegates all representation concerns to another
171+
type, it is incompatible with all other `repr(..)` attributes. It also cannot be
172+
applied to enums, unions, empty structs, structs whose fields are all
173+
zero-sized, or structs with *multiple* non-zero-sized fields.
174+
175+
[PhantomData]: https://doc.rust-lang.org/std/marker/struct.PhantomData.html
176+
[nomicon-phantom]: https://doc.rust-lang.org/nomicon/phantom-data.html

Diff for: src/librustc/diagnostics.rs

+63-1
Original file line numberDiff line numberDiff line change
@@ -2003,7 +2003,69 @@ that refers to itself. That is permitting, since the closure would be
20032003
invoking itself via a virtual call, and hence does not directly
20042004
reference its own *type*.
20052005
2006-
"##, }
2006+
"##,
2007+
2008+
E0692: r##"
2009+
A `repr(transparent)` type was also annotated with other, incompatible
2010+
representation hints.
2011+
2012+
Erroneous code example:
2013+
2014+
```compile_fail,E0692
2015+
#![feature(repr_transparent)]
2016+
2017+
#[repr(transparent, C)] // error: incompatible representation hints
2018+
struct Grams(f32);
2019+
```
2020+
2021+
A type annotated as `repr(transparent)` delegates all representation concerns to
2022+
another type, so adding more representation hints is contradictory. Remove
2023+
either the `transparent` hint or the other hints, like this:
2024+
2025+
```
2026+
#![feature(repr_transparent)]
2027+
2028+
#[repr(transparent)]
2029+
struct Grams(f32);
2030+
```
2031+
2032+
Alternatively, move the other attributes to the contained type:
2033+
2034+
```
2035+
#![feature(repr_transparent)]
2036+
2037+
#[repr(C)]
2038+
struct Foo {
2039+
x: i32,
2040+
// ...
2041+
}
2042+
2043+
#[repr(transparent)]
2044+
struct FooWrapper(Foo);
2045+
```
2046+
2047+
Note that introducing another `struct` just to have a place for the other
2048+
attributes may have unintended side effects on the representation:
2049+
2050+
```
2051+
#![feature(repr_transparent)]
2052+
2053+
#[repr(transparent)]
2054+
struct Grams(f32);
2055+
2056+
#[repr(C)]
2057+
struct Float(f32);
2058+
2059+
#[repr(transparent)]
2060+
struct Grams2(Float); // this is not equivalent to `Grams` above
2061+
```
2062+
2063+
Here, `Grams2` is a not equivalent to `Grams` -- the former transparently wraps
2064+
a (non-transparent) struct containing a single float, while `Grams` is a
2065+
transparent wrapper around a float. This can make a difference for the ABI.
2066+
"##,
2067+
2068+
}
20072069

20082070

20092071
register_diagnostics! {

Diff for: src/librustc/hir/check_attr.rs

+21-4
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ impl<'a, 'tcx> CheckAttrVisitor<'a, 'tcx> {
9292
let mut int_reprs = 0;
9393
let mut is_c = false;
9494
let mut is_simd = false;
95+
let mut is_transparent = false;
9596

9697
for hint in &hints {
9798
let name = if let Some(name) = hint.name() {
@@ -137,6 +138,14 @@ impl<'a, 'tcx> CheckAttrVisitor<'a, 'tcx> {
137138
continue
138139
}
139140
}
141+
"transparent" => {
142+
is_transparent = true;
143+
if target != Target::Struct {
144+
("a", "struct")
145+
} else {
146+
continue
147+
}
148+
}
140149
"i8" | "u8" | "i16" | "u16" |
141150
"i32" | "u32" | "i64" | "u64" |
142151
"isize" | "usize" => {
@@ -155,14 +164,22 @@ impl<'a, 'tcx> CheckAttrVisitor<'a, 'tcx> {
155164
.emit();
156165
}
157166

167+
// Just point at all repr hints if there are any incompatibilities.
168+
// This is not ideal, but tracking precisely which ones are at fault is a huge hassle.
169+
let hint_spans = hints.iter().map(|hint| hint.span);
170+
171+
// Error on repr(transparent, <anything else>).
172+
if is_transparent && hints.len() > 1 {
173+
let hint_spans: Vec<_> = hint_spans.clone().collect();
174+
span_err!(self.tcx.sess, hint_spans, E0692,
175+
"transparent struct cannot have other repr hints");
176+
}
158177
// Warn on repr(u8, u16), repr(C, simd), and c-like-enum-repr(C, u8)
159178
if (int_reprs > 1)
160179
|| (is_simd && is_c)
161180
|| (int_reprs == 1 && is_c && is_c_like_enum(item)) {
162-
// Just point at all repr hints. This is not ideal, but tracking
163-
// precisely which ones are at fault is a huge hassle.
164-
let spans: Vec<_> = hints.iter().map(|hint| hint.span).collect();
165-
span_warn!(self.tcx.sess, spans, E0566,
181+
let hint_spans: Vec<_> = hint_spans.collect();
182+
span_warn!(self.tcx.sess, hint_spans, E0566,
166183
"conflicting representation hints");
167184
}
168185
}

Diff for: src/librustc/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
#![feature(box_syntax)]
4545
#![feature(conservative_impl_trait)]
4646
#![feature(const_fn)]
47+
#![feature(copy_closures, clone_closures)]
4748
#![feature(core_intrinsics)]
4849
#![feature(drain_filter)]
4950
#![feature(dyn_trait)]

Diff for: src/librustc/ty/mod.rs

+5-1
Original file line numberDiff line numberDiff line change
@@ -1499,8 +1499,9 @@ bitflags! {
14991499
const IS_C = 1 << 0;
15001500
const IS_PACKED = 1 << 1;
15011501
const IS_SIMD = 1 << 2;
1502+
const IS_TRANSPARENT = 1 << 3;
15021503
// Internal only for now. If true, don't reorder fields.
1503-
const IS_LINEAR = 1 << 3;
1504+
const IS_LINEAR = 1 << 4;
15041505

15051506
// Any of these flags being set prevent field reordering optimisation.
15061507
const IS_UNOPTIMISABLE = ReprFlags::IS_C.bits |
@@ -1540,6 +1541,7 @@ impl ReprOptions {
15401541
flags.insert(match r {
15411542
attr::ReprC => ReprFlags::IS_C,
15421543
attr::ReprPacked => ReprFlags::IS_PACKED,
1544+
attr::ReprTransparent => ReprFlags::IS_TRANSPARENT,
15431545
attr::ReprSimd => ReprFlags::IS_SIMD,
15441546
attr::ReprInt(i) => {
15451547
size = Some(i);
@@ -1567,6 +1569,8 @@ impl ReprOptions {
15671569
#[inline]
15681570
pub fn packed(&self) -> bool { self.flags.contains(ReprFlags::IS_PACKED) }
15691571
#[inline]
1572+
pub fn transparent(&self) -> bool { self.flags.contains(ReprFlags::IS_TRANSPARENT) }
1573+
#[inline]
15701574
pub fn linear(&self) -> bool { self.flags.contains(ReprFlags::IS_LINEAR) }
15711575

15721576
pub fn discr_type(&self) -> attr::IntType {

Diff for: src/librustc_lint/types.rs

+14-3
Original file line numberDiff line numberDiff line change
@@ -416,7 +416,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
416416
}
417417
match def.adt_kind() {
418418
AdtKind::Struct => {
419-
if !def.repr.c() {
419+
if !def.repr.c() && !def.repr.transparent() {
420420
return FfiUnsafe("found struct without foreign-function-safe \
421421
representation annotation in foreign module, \
422422
consider adding a #[repr(C)] attribute to the type");
@@ -427,13 +427,24 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
427427
adding a member to this struct");
428428
}
429429

430-
// We can't completely trust repr(C) markings; make sure the
431-
// fields are actually safe.
430+
// We can't completely trust repr(C) and repr(transparent) markings;
431+
// make sure the fields are actually safe.
432432
let mut all_phantom = true;
433433
for field in &def.non_enum_variant().fields {
434434
let field_ty = cx.fully_normalize_associated_types_in(
435435
&field.ty(cx, substs)
436436
);
437+
// repr(transparent) types are allowed to have arbitrary ZSTs, not just
438+
// PhantomData -- skip checking all ZST fields
439+
if def.repr.transparent() {
440+
let is_zst = (cx, cx.param_env(field.did))
441+
.layout_of(field_ty)
442+
.map(|layout| layout.is_zst())
443+
.unwrap_or(false);
444+
if is_zst {
445+
continue;
446+
}
447+
}
437448
let r = self.check_type_for_ffi(cache, field_ty);
438449
match r {
439450
FfiSafe => {

0 commit comments

Comments
 (0)