The key to this is that you can read arbitrary data from a raw pointer without the need to bind it. The right terminology here would be that you can
load data from the pointer, as with
Reframed slightly: when you bind memory in Swift, you are asserting to the compiler and optimizer that that memory can only contain data of a certain type, be it
MyCustomType. Memory can only be bound to a single type at a time, cannot be arbitrarily rebound (see
UnsafePointer.withMemoryRebound(to:capacity:) for some more nuance), and cannot be unbound without being deallocated.
Raw memory, on the other hand, does not have these restrictions, and can be accessed byte-wise as any type, so long as you get the stride and alignment correct. From the
Each byte in memory is viewed as a
UInt8 value independent of the type of values held in that memory. Reading from memory through a raw buffer is an untyped operation.
In addition to its collection interface, an
UnsafeRawBufferPointer instance also supports the
load(fromByteOffset:as:) method provided by
UnsafeRawPointer , including bounds checks in debug mode.
Leaving memory unbound makes reading from it significantly more manual (you have to correctly manage byte offsets and ensure your stride and alignment are correct), but it means that you can safely read anything you want out of it.
So this is how both
Data can be sequences of
UInt8 — the
Element type is really a "byte" type which you're getting raw access to. The difference between that and
UnsafeBufferPointer<UInt8> is... subtle... if there is truly a meaningful difference. The language can guarantee that the compiler and optimizer treat
UnsafeRawBufferPointer and vice versa, and guarantee that it is always safe that rebinding that way is safe — it just doesn't, yet. If and when it does,
Data can certainly hand out an
UnsafeBufferPointer<UInt8> safely; until then, the safest thing to do is have it hand out
This whole thing started for me when looking at trying to
fwrite some information. Sometimes that information comes in the form of a
String sometimes in the form of
Data output of
JSONEncoder . I tried to unify that by way of
withContiguousStorageIfAvailable , clearly unsuccessfully. In light of this discussion, it feels like
JSONEncoder should be returning
UnsafeBufferPointer<UInt8> and not
I don't have access to a machine with Swift on it at the moment to verify, but IIRC,
fwrite takes a
const void *, which I believe should export to Swift as an
UnsafeRawPointer. If this is the case, you should still be able to abstract over these types using a custom protocol — because
UnsafeBufferPointer<UInt8> itself conforms to
ContiguousBytes, you should be able to get a consistent buffer from both
Data, and write that out.
FWIW, this is definitely a really thorny topic! I think life would be a lot simpler if the language could make some clearer guarantees about convertibility between
UInt8 pointers and
Raw pointers, but with the goal here being safety and increasing the bar from how easy it is to make memory-aliasing mistakes in other languages like C, it can be a bit tough to prevent easy mistakes.