Note that, like any FRU, this does still require that all fields are public (visible at point of construction). Also, just for clarity, PAD
still needs to be the same base type (MyType
); RFC#2528 only allows generic parameters to be replaced.
And since it's not mentioned here yet, there are (as far as I'm aware) two main ways to emulate explicit padding, both of which (likely) don't quite emulate it perfectly, and have different tradeoffs.
The first and simpler option you're more likely to see is just using MaybeUninit<Pad>
directly, where Pad
is some [uN; M]
. The latter is more involved, looking like the following:
#[repr(u8)]
enum ZeroPadByte { Value = 0 }
#[repr(C)]
struct ZeroPad<Type> {
size: [ZeroPad; { size_of::<Type>() }],
align: AlignAs<Type>,
}
struct AlignAs<Type>([Type; 0]);
unsafe impl Send for AlignAs<_> {}
unsafe impl Sync for AlignAs<_> {}
impl Unpin for AlignAs<_> {}
impl UnwindSafe for AlignAs<_> {}
impl RefUnwindSafe for AlignAs<_> {}
// impl well-known traits as desired, e.g. derivable
You can also use MaybeUninit<u8>
instead of ZeroPadByte
to cut the difference. There's two dimensions of differences between the options and actual implicit padding:
- Autotraits. When using
AlignAs
, we cover any autotrait optouts of the example type, so it doesn't impact the autotrait inference of the containing type. (No impact for [uN; N]
.) Unfortunately, still requires the example type to be Copy
in order to be Copy
itself, until we eventually hopefully get impl<T> Copy for [T; 0]
in std.
- Initialization. When doing a typed copy, implicit padding bytes are (likely) reset to an uninitialized state. (The primary benefit is that the compiler would be allowed to copy those bytes or not, at its optimization whims.) With
MaybeUninit
, any byte pattern is permitted and preserved exactly, as would with an untyped copy. With ZeroPadByte
, the padding is guaranteed/required to be zero-initialized, allowing the use of nonzero values for niche optimization.
For this specific use case of target-independent layout/manipulation, though, it probably makes sense to use nothing less restrictive than MaybeUninit
, though; keeping autotraits the same cross-platform is desirable, and I believe preserving the opaque bytes from a different target's representation is desired, which makes guaranteed-zero or reset-to-uninit padding undesirable.
I'd generally prefer to just use MaybeUninit<[uN; M]>
, since that's simpler, more easily understood, and the most predictable (combined with an assertion of no implicit padding); if in the future we get a way to specify real padding, MaybeUninit
fields can be swapped out fairly easily, if they've always been left uninitialized. Second would actually be just utilizing an aligning [uN; 0]
field (or an Aligned<T, uN>
wrapper), if that's sufficient for the use case. I'd only pull out ZeroPad
if the niching benefit is notable and the type is Rust-native.