Swift's enumerations are smart about the spare bits that exist in pointers. Consider the output of this small script:
class Buffer {
var test: [Int] = []
}
enum TaggedPointer {
case buffer(Buffer)
case inline
}
print(MemoryLayout<TaggedPointer>.size)
The output of this script is 8, demonstrating that the discriminator bit for the enum
is stuffed into the pointer to the Buffer
class.
But you've probably noticed that my other case is empty. The reason it's empty is because the Swift compiler needs to know what what you store in there is smaller than 64-bits. Consider the program below:
class Buffer {
var buffer: [Int] = []
}
enum TaggedPointer {
case buffer(Buffer)
case inline(Int32)
}
print(MemoryLayout<TaggedPointer>.size)
This also produces 8. As Swift knows that Int32
only needs 4 bytes, it knows that it can safely use one of the spare bits in the Buffer
pointer. However, if you try to put Int64
in there, it all goes wrong:
class Buffer {
var buffer: [Int] = []
}
enum TaggedPointer {
case buffer(Buffer)
case inline(Int64)
}
print(MemoryLayout<TaggedPointer>.size)
The output of this script is 9. This is because Swift doesn't know that you promise not to use the top bit of your Int64
(or UInt64
), and so cannot safely use the spare bit in the buffer pointer anymore. So you need to be careful to ensure that the size of the other case really is smaller than 64 bits and that Swift knows it.
The other issue is if you're trying to stuff pointers that Swift doesn't understand. For example, consider this program:
enum EmptyTaggedPointer {
case buffer(UnsafePointer<UInt8>)
case inline
}
enum TaggedPointer {
case buffer(UnsafePointer<UInt8>)
case inline(Int32)
}
print(MemoryLayout<EmptyTaggedPointer>.size)
print(MemoryLayout<TaggedPointer>.size)
This program prints 8
and then 9
. Why?
The short answer is that the UnsafePointer
family of things all have exactly one uninhabited bit pattern: the all-zero case. This is because the NULL
representation of an unsafe pointer corresponds to Optional<UnsafePointer>.none
, and so if you have a non-optional one by definition that pattern is uninhabited.
But because Swift doesn't know anything about the provenance of this pointer, it doesn't know what the rules for it are. It can't know whether the tag bits are important or not, so it can't use them.
The TL;DR here is: this might work, if you fit exactly into the use-case that enum
handles well. Otherwise you'll have to bit-bang this yourself with unsafe pointer.