Swift has never guaranteed the in-memory layout (size and alignment) of structs and classes, with only a few exceptions:
- A struct with one element has the same layout as that element.
- Class instances have two words (Int-sized and -aligned fields) at the start of the allocation, so class instances are always word-aligned.
- Structs defined from C will have the same size and alignment as they do in C (as one would hope).
Tuples have always had their own guarantee: if all the elements are the same type, they will be laid out in order by stride (size rounded up to alignment), just like a fixed-sized array in C. This applies even if the elements are Swift types, which has come up when trying to manipulate elements in tuples through pointers to the whole tuple. The most recent citation for this is this year's WWDC's "Safely manage pointers in Swift":
(Thanks for looking that up and sending it my way, @Lily_Ballard.)
There's an additional constraint with ABI stability: different compilers must be able to directly manipulate
@frozenstructs that come from library-evolution-enabled modules, so the layout of these structs is guaranteed as well. (This layout happens to be "lay out fields in order, rounding up only for alignment" on Apple platforms.) This does not extend to non-frozen structs or to structs in non-library-evolution-enabled modules, because there's no agreement that has to happen across versions of the compiler (and runtimes) about the layout of these structs.
The same does apply to tuples (as part of ABI stability), which are basically treated as ad hoc frozen structs. In theory tuples only referenced from a single module could use a totally different in-memory layout from everywhere else, but you'd have to prove that there was no access to those tuples from somewhere else, and that's probably not worth it.
All of this only applies to in-memory representations, which means "those you could theoretically get a pointer to". If I pass a tuple by value, the compiler is free to split it up across multiple machine registers; if I have a tuple local variable, the compiler can throw away fields that are written to but never read. (You can't observe the memory layout of something you don't get a pointer to,
MemoryLayoutnotwithstanding, so don't worry about it!)
The Question: Are non-homogeneous tuples guaranteed to use the in-order, rounding-up layout that frozen structs on Apple platforms use?
There are past comments from @John_McCall that imply the answer is yes...
...but I would like this to be written down somewhere less ephemeral than old forum threads. On the other hand, if that answer is being retracted (i.e. only applied to earlier versions of Swift), I would like that to be written down somewhere. So I'm putting this in Evolution/Pitches because a proposal is the closest the community has to "request official word from the core team".
Why does this matter?
I'm not sure it does.
Tuple layout still isn't going to be the same as C struct layout because even on platforms where C uses the same layout algorithm (most of them), C doesn't differentiate between size and stride. (Instead, the size also gets rounded up to the alignment.)
Having a way to control memory layout of Swift types is something I think we'll want to do eventually, but others have disagreed, saying that manual type layout should be left to C headers (perhaps using compiler extensions like
#pragma pack). I think both camps would agree that controlling memory layout by always using tuple types would be a bad solution, though.
One "feature" of in-order layout is that pointers to later fields compare greater than pointers to earlier fields. I have never seen this be useful for anything, however.
The idea of automatically packing fields together comes up now and again (for fields with Bool or small-enum types). I think actually making that change for structs would be quite the upheaval due the existence of
MemoryLayout.offset(of:)(if not technically source-breaking or ABI-breaking). But even sidestepping that, we already have the homogeneous tuple guarantee—that
(Bool, Bool, Bool)is equivalent to
boolin C—and it seems weird to me that
(Bool, Bool, Bool, Int8)would make such a different layout choice.
Even without motivation, though, the question of tuple layout resurfaces every now and then, and people are also surprised to learn that struct and class layout are not guaranteed. (I wish we hadn't stuck with something so close to C layout on our platforms, so it would have been more obvious!) So I think it's worth writing down what is and isn't guaranteed about tuples on non-ABI-stable platforms.
- Does the core team have any comments?
- Does anyone have any use cases for knowing the layout of tuples, specifically?