I'm trying to learn how unsafe memory works in Swift but confused by the following code. The code below works fine when data is an Array<Float> but prints garbage when data is declared a ContiguousArray<Float>. Why is this?
// When this is a ContiguousArray<Float> code prints garbage, but when it's simply an Array<Float> code works fine.
var data: ContiguousArray<Float> = [0.1,0.2,0.3]
func test() {
let unsafeMutableRawPointer = UnsafeMutableRawPointer.allocate(byteCount: data.count*MemoryLayout<Float>.stride, alignment: MemoryLayout<Float>.alignment)
unsafeMutableRawPointer.copyMemory(from: &data, byteCount: data.count * MemoryLayout<Float>.stride)
let p = unsafeMutableRawPointer.bindMemory(to: Float.self, capacity: data.count)
print(p.pointee) // Prints 0.1 when data is Array but garbage when data is ContiguousArray
print(p.advanced(by: 1).pointee) // Prints 0.2 when data is Array but garbage when data is ContiguousArray
print(p.advanced(by: 2).pointee) // Prints 0.3 when data is Array but garbage when data is ContiguousArray
}
test()
This is how it looks inside (use this information at your own risk!)
Array:
cdcccc3d cdcc4c3e 9a99993e
these are your three numbers (in little endian form)
ContiguousArray is more complicated, it contains a pointer inside (double indirection). If you dump enough bytes at the address that the pointer points to - you'd see your numbers at some offset:
A side note: you can pass &floats to a function that accepts UnsafePointer<Float> when floats are of type Array<Float>. But you can't do this when the type is ContiguousArray<Float>.
PS. worth mentioning that Array may also store instances of NSArray.
That's interesting, I guess I just assumed (naively) that it would copy only the contiguous bytes of just the data in the array and not the extra stuff. It's funny though how array works fine, I would assume that arrays representation would be more complicated since it's not even guaranteed to be contiguous.
Regardless none of what I'm doing seems safe since I have no idea if at any point these representations could change.
Can you point me to more documentation about that side note? I find it strange that it doesn't work for ContiguousArray.
The bigger picture is that I was originally working on a project involving the graphics API Metal and I needed to copy data from an array into a UnsafeMutableRawPointer exposed by Metal. One of my arrays happened to be a ContiguousArray instead of a regular Array which led to this discovery. I still don't fully understand how safe it is to even use Array in this way since what if, for example, they end up changing the internal structure to be like ContiguousArray. I suppose maybe there is some documentation somewhere that mentions this. Maybe I need to roll my own solution with say UnsafeBufferPointer and never copy raw memory from Array/ContiguousArray to be safe.
The method copyMemory(from:byteCount:) takes an UnsafeRawPointer as the first argument.
By using inout syntax, you're taking advantage of Swift's implicit bridging. This allows you to read the raw memory from an instance of any type.
Uniquely for Array, however, a temporary pointer to the elements of the array is created implicitly; there is no such magic for ContiguousArray. Your code relies on this magic implicit behavior to work as you'd expect, which is why it doesn't do what you want when you use ContiguousArray values.
In order to copy from the elements of a ContiguousArray, you should use withUnsafeBytes so that you have a temporary pointer to the elements rather than the container:
let unsafeMutableRawPointer = /* ... */
data.withUnsafeBytes {
unsafeMutableRawPointer.copyMemory(from: $0.baseAddress!, byteCount: /* ... */)
}
let p = /* etc. */
You can, of course, use the same code when data is a plain Array, instead of relying on the special-cased implicit behavior.
@tera's answer is not quite correct as to how an Array value "looks on the inside" as compared to a ContiguousArray value, and you do not have to rely on knowledge of the internal representations in order to do what you want to do correctly. You simply need to be aware of when Swift is implicitly doing some of the work for you and how to achieve the same thing with public APIs.
For more on the use of UnsafeRawPointer's implicit casting and bridging behavior, see the documentation of that type.
Is there a way to opt-out of that implicit bridging? I'd like to see the raw bytes of Array (similar to how I can do this for ContinuousArray). For Educational/Debugging purposes only.
This behavior is implicit to passing an inout value of type Array where an argument of type UnsafeRawPointer is expected. You don't get to opt out of this behavior if all three conditions apply, but you also don't get this behavior if these three conditions don't all apply.
Reading the source code would probably be a simpler way of figuring things out, but you can get your unsafe raw (buffer) pointer by calling withUnsafeBytes(of: data) if you'd prefer.
Thank you for the brilliant explanation, the "magic" bridging that is used for Arrays is what was naively expecting me to be able to just copy raw memory. I now see I must rely on the underling storage pointers using withUnsafeBytes and the like. I still find it strange that this behavior is implicit on the Array type but not the ContiguousArray, which ultimately led to some nasty bugs in my project. I suppose this implicit behavior was added to make it easier when working with C. All very interesting though, thanks again to you and @tera for assisting me with this issue.