Skip to content

Commit a44ca87

Browse files
committed
Import #[repr(transparent)] docs from the Unstable book
… as the feature is ready for stabilization: rust-lang/rust#43036 (comment)
1 parent 320c8e7 commit a44ca87

File tree

1 file changed

+162
-0
lines changed

1 file changed

+162
-0
lines changed

src/type-layout.md

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,166 @@ a `packed` type cannot transitively contain another `align`ed type.
294294
> possible to [safely create unaligned pointers to `packed` fields][27060].
295295
> Like all ways to create undefined behavior in safe Rust, this is a bug.
296296
297+
### The `transparent` representation
298+
299+
#### Background
300+
301+
It's sometimes useful to add additional type safety by introducing *newtypes*.
302+
For example, code that handles numeric quantities in different units such as
303+
millimeters, centimeters, grams, kilograms, etc. may want to use the type system
304+
to rule out mistakes such as adding millimeters to grams:
305+
306+
```rust
307+
use std::ops::Add;
308+
309+
struct Millimeters(f64);
310+
struct Grams(f64);
311+
312+
impl Add<Millimeters> for Millimeters {
313+
type Output = Millimeters;
314+
315+
fn add(self, other: Millimeters) -> Millimeters {
316+
Millimeters(self.0 + other.0)
317+
}
318+
}
319+
320+
// Likewise: impl Add<Grams> for Grams {}
321+
```
322+
323+
Other uses of newtypes include using `PhantomData` to add lifetimes to raw
324+
pointers or to implement the "phantom types" pattern. See the [PhantomData]
325+
documentation and [the Nomicon][nomicon-phantom] for more details.
326+
327+
The added type safety is especially useful when interacting with C or other
328+
languages. However, in those cases we need to ensure the newtypes we add do not
329+
introduce incompatibilities with the C ABI.
330+
331+
#### Newtypes in FFI
332+
333+
Luckily, `repr(C)` newtypes are laid out just like the type they wrap on all
334+
platforms which Rust currently supports, and likely on many more. For example,
335+
consider this C declaration:
336+
337+
```C
338+
struct Object {
339+
double weight; //< in grams
340+
double height; //< in millimeters
341+
// ...
342+
}
343+
344+
void frobnicate(struct Object *);
345+
```
346+
347+
While using this C code from Rust, we could add `repr(C)` to the `Grams` and
348+
`Millimeters` newtypes introduced above and use them to add some type safety
349+
while staying compatible with the memory layout of `Object`:
350+
351+
```rust,no_run
352+
#[repr(C)]
353+
struct Grams(f64);
354+
355+
#[repr(C)]
356+
struct Millimeters(f64);
357+
358+
#[repr(C)]
359+
struct Object {
360+
weight: Grams,
361+
height: Millimeters,
362+
// ...
363+
}
364+
365+
extern {
366+
fn frobnicate(_: *mut Object);
367+
}
368+
```
369+
370+
This works even when adding some `PhantomData` fields, because they are
371+
zero-sized and therefore don't have to affect the memory layout.
372+
373+
However, there's more to the ABI than just memory layout: there's also the
374+
question of how function call arguments and return values are passed. Many
375+
common ABI treat a struct containing a single field differently from that field
376+
itself, at least when the field is a scalar (e.g., integer or float or pointer).
377+
378+
To continue the above example, suppose the C library also exposes a function
379+
like this:
380+
381+
```C
382+
double calculate_weight(double height);
383+
```
384+
385+
Using our newtypes on the Rust side like this will cause an ABI mismatch on many
386+
platforms:
387+
388+
```rust,ignore
389+
extern {
390+
fn calculate_weight(height: Millimeters) -> Grams;
391+
}
392+
```
393+
394+
For example, on x86_64 Linux, Rust will pass the argument in an integer
395+
register, while the C function expects the argument to be in a floating-point
396+
register. Likewise, the C function will return the result in a floating-point
397+
register while Rust will expect it in an integer register.
398+
399+
Note that this problem is not specific to floats: To give another example,
400+
32-bit x86 linux will pass and return `struct Foo(i32);` on the stack while
401+
`i32` is placed in registers.
402+
403+
#### Enter `repr(transparent)`
404+
405+
So while `repr(C)` happens to do the right thing with respect to memory layout,
406+
it's not quite the right tool for newtypes in FFI. Instead of declaring a C
407+
struct, we need to communicate to the Rust compiler that our newtype is just for
408+
type safety on the Rust side. This is what `repr(transparent)` does.
409+
410+
The attribute can be applied to a newtype-like structs that contains a single
411+
field. It indicates that the newtype should be represented exactly like that
412+
field's type, i.e., the newtype should be ignored for ABI purpopses: not only is
413+
it laid out the same in memory, it is also passed identically in function calls.
414+
415+
In the above example, the ABI mismatches can be prevented by making the newtypes
416+
`Grams` and `Millimeters` transparent like this:
417+
418+
```rust
419+
#[repr(transparent)]
420+
struct Grams(f64);
421+
422+
#[repr(transparent)]
423+
struct Millimeters(f64);
424+
```
425+
426+
In addition to that single field, any number of zero-sized fields are permitted,
427+
including but not limited to `PhantomData`:
428+
429+
```rust
430+
use std::marker::PhantomData;
431+
432+
struct Foo { /* ... */ }
433+
434+
#[repr(transparent)]
435+
struct FooPtrWithLifetime<'a>(*const Foo, PhantomData<&'a Foo>);
436+
437+
#[repr(transparent)]
438+
struct NumberWithUnit<T, U>(T, PhantomData<U>);
439+
440+
struct CustomZst;
441+
442+
#[repr(transparent)]
443+
struct PtrWithCustomZst<'a> {
444+
ptr: FooPtrWithLifetime<'a>,
445+
some_marker: CustomZst,
446+
}
447+
```
448+
449+
Transparent structs can be nested: `PtrWithCustomZst` is also represented
450+
exactly like `*const Foo`.
451+
452+
Because `repr(transparent)` delegates all representation concerns to another
453+
type, it is incompatible with all other `repr(..)` attributes. It also cannot be
454+
applied to enums, unions, empty structs, structs whose fields are all
455+
zero-sized, or structs with *multiple* non-zero-sized fields.
456+
297457
[`align_of_val`]: ../std/mem/fn.align_of_val.html
298458
[`size_of_val`]: ../std/mem/fn.size_of_val.html
299459
[`align_of`]: ../std/mem/fn.align_of.html
@@ -304,3 +464,5 @@ a `packed` type cannot transitively contain another `align`ed type.
304464
[zero-variant enumerations]: items/enumerations.html#zero-variant-enums
305465
[undefined behavior]: behavior-considered-undefined.html
306466
[27060]: https://github.com/rust-lang/rust/issues/27060
467+
[PhantomData]: https://doc.rust-lang.org/std/marker/struct.PhantomData.html
468+
[nomicon-phantom]: https://doc.rust-lang.org/nomicon/phantom-data.html

0 commit comments

Comments
 (0)