Alternative representations

Rust allows you to specify alternative data layout strategies from the default. There's also the unsafe code guidelines (note that it's NOT normative).

repr(C)

This is the most important repr. It has fairly simple intent: do what C does. The order, size, and alignment of fields is exactly what you would expect from C or C++. The type is also passed across extern "C" function call boundaries the same way C would pass the corresponding type. Any type you expect to pass through an FFI boundary should have repr(C), as C is the lingua-franca of the programming world. This is also necessary to soundly do more elaborate tricks with data layout such as reinterpreting values as a different type.

We strongly recommend using rust-bindgen and/or cbindgen to manage your FFI boundaries for you. The Rust team works closely with those projects to ensure that they work robustly and are compatible with current and future guarantees about type layouts and reprs.

The interaction of repr(C) with Rust's more exotic data layout features must be kept in mind. Due to its dual purpose as "for FFI" and "for layout control", repr(C) can be applied to types that will be nonsensical or problematic if passed through the FFI boundary.

repr(transparent)

#[repr(transparent)] can only be used on a struct or single-variant enum that has a single non-zero-sized field (there may be additional zero-sized fields). The effect is that the layout and ABI of the whole struct/enum is guaranteed to be the same as that one field.

NOTE: There's a transparent_unions nightly feature to apply repr(transparent) to unions, but it hasn't been stabilized due to design concerns. See the tracking issue for more details.

The goal is to make it possible to transmute between the single field and the struct/enum. An example of that is UnsafeCell, which can be transmuted into the type it wraps (UnsafeCell also uses the unstable no_niche, so its ABI is not actually guaranteed to be the same when nested in other types).

Also, passing the struct/enum through FFI where the inner field type is expected on the other side is guaranteed to work. In particular, this is necessary for struct Foo(f32) or enum Foo { Bar(f32) } to always have the same ABI as f32.

This repr is only considered part of the public ABI of a type if either the single field is pub, or if its layout is documented in prose. Otherwise, the layout should not be relied upon by other crates.

More details are in the RFC 1758 and the RFC 2645.

repr(u*), repr(i*)

These specify the size and sign to make a fieldless enum. If the discriminant overflows the integer it has to fit in, it will produce a compile-time error. You can manually ask Rust to allow this by setting the overflowing element to explicitly be 0. However Rust will not allow you to create an enum where two variants have the same discriminant.

The term "fieldless enum" only means that the enum doesn't have data in any of its variants. A fieldless enum without a repr is still a Rust native type, and does not have a stable layout or representation. Adding a repr(u*)/repr(i*) causes it to be treated exactly like the specified integer type for layout purposes (except that the compiler will still exploit its knowledge of "invalid" values at this type to optimize enum layout, such as when this enum is wrapped in Option). Note that the function call ABI for these types is still in general unspecified, except that across extern "C" calls they are ABI-compatible with C enums of the same sign and size.

If the enum has fields, the effect is similar to the effect of repr(C) in that there is a defined layout of the type. This makes it possible to pass the enum to C code, or access the type's raw representation and directly manipulate its tag and fields. See the RFC for details.

These reprs have no effect on a struct.

Adding an explicit repr(u*), repr(i*), or repr(C) to an enum with fields suppresses the null-pointer optimization, like:

# use std::mem::size_of;
enum MyOption<T> {
    Some(T),
    None,
}

#[repr(u8)]
enum MyReprOption<T> {
    Some(T),
    None,
}

assert_eq!(8, size_of::<MyOption<&u16>>());
assert_eq!(16, size_of::<MyReprOption<&u16>>());

This optimization still applies to fieldless enums with an explicit repr(u*), repr(i*), or repr(C).

repr(packed), repr(packed(n))

repr(packed(n)) (where n is a power of two) forces the type to have an alignment of at most n. Most commonly used without an explicit n, repr(packed) is equivalent to repr(packed(1)) which forces Rust to strip any padding, and only align the type to a byte. This may improve the memory footprint, but will likely have other negative side-effects.

In particular, most architectures strongly prefer values to be naturally aligned. This may mean that unaligned loads are penalized (x86), or even fault (some ARM chips). For simple cases like directly loading or storing a packed field, the compiler might be able to paper over alignment issues with shifts and masks. However if you take a reference to a packed field, it's unlikely that the compiler will be able to emit code to avoid an unaligned load.

As this can cause undefined behavior, the lint has been implemented and it will become a hard error.

repr(packed)/repr(packed(n)) is not to be used lightly. Unless you have extreme requirements, this should not be used.

This repr is a modifier on repr(C) and repr(Rust). For FFI compatibility you most likely always want to be explicit: repr(C, packed).

repr(align(n))

repr(align(n)) (where n is a power of two) forces the type to have an alignment of at least n.

This enables several tricks, like making sure neighboring elements of an array never share the same cache line with each other (which may speed up certain kinds of concurrent code).

This is a modifier on repr(C) and repr(Rust). It is incompatible with repr(packed).