This is a really interesting comment, and something I've wondered about for a long time. I don't feel it's very clear what the role of pointers in Swift actually is - are they only for C interop?
In my opinion, typed pointers should over time become residual types almost entirely for C interop.
If so, why are typed pointers so pervasive (even to pure Swift types which aren't exposed to C),
For pure Swift, that's a mistake that needs to be gradually corrected. It's probably because programs are verbatim ported from C, and convenient alternatives are not yet part of the Swift standard library.
and why is it so difficult for Swift code to reinterpret memory as a different type?
Swift makes it easy to reinterpret memory relative to C (or at least it will once we support misalignment). There's no need for type punning. Swift's raw pointers do force you to be explicit at each point that memory is being reinterpreted, which corrects the fundamental mistake in C. But you can easily build abstractions on top of raw pointers that make it type-safe and convenient.
The single-slide example from the WWDC talk:
struct BufferView<Element> : RandomAccessCollection {
let rawBytes: UnsafeRawBufferPointer
let count: Int
init(reinterpret rawBytes: UnsafeRawBufferPointer, as: Element.Type) {
self.rawBytes = rawBytes
self.count = rawBytes.count / MemoryLayout<Element>.stride
precondition(self.count * MemoryLayout<Element>.stride == rawBytes.count)
precondition(Int(bitPattern: rawBytes.baseAddress).isMultiple(of: MemoryLayout<Element>.alignment))
}
public var startIndex: Int { 0 }
public var endIndex: Int { count }
subscript(index: Int) -> Element {
rawBytes.load(fromByteOffset: index * MemoryLayout<Element>.stride, as: Element.self)
}
}
Before officially proposing such a type, we need to consider broader issues of memory ownership and its relationship with existing types and possibly new "buffer" types.
I'm not really thrilled that the data passed to the function must be bound to type Float. That might be difficult if the data is provided as raw memory from a 3rd-party library (e.g. read from a file, or from the network). A function can declare that it wants data which can be interpreted as Floats, but binding is much a stronger commitment that requires you to track the provenance of the memory.
This is a legacy C API problem. Definitely should never happen in pure Swift code.
What would be better IMO is for a type that means "raw memory interpreted as Floats". Unfortunately, that wouldn't work with existing APIs such as Collection.withContiguousStorageIfAvailable or AccelerateBuffer.withUnsafeBufferPointer, for the same reason that UnsafeRawBufferPointer doesn't implement wCSIA - "raw memory interpreted as X" is not the same thing as "raw memory bound to X". Even when X is UInt8, apparently.
The standard library's performance hooks are incompatible with untyped byte buffers. We'll propose an UnsafeRawPointer.withMemoryRebound
as a work around. Although, ideally future APIs will be based on a more general BufferView
type.
Ideally, if we did introduce a new interpreted-memory type, it would also guarantee the lifetime of the memory it points to.
Exactly. That's one of the reasons it's not part of the standard library yet. But this is a discussion for an upcoming proposal.
Also, I don't think it has been discussed whether memory rebinding is safe now that we have language support for concurrency. Imagine that a function using withMemoryRebound is running in parallel with another function, which reads from the same buffer but using its original type. As it is currently documented, I would assume that isn't safe.
In the context of strict aliasing it is safe. It may be unsafe for other reasons of course: race conditions, exclusive access if the memory is associated with a variable, value type safety (as opposed to pointer type safety).
If that is the case, would it be unwise to make memory rebinding implicit? Or is it safe because the implicit conversions only apply to memory disappearing in to C-land?
"Implicit" rebinding only happens at the end of the withMemoryRebound
closure's scope. The only rule is that the same program thread can only access that memory with the pointer type that was passed to the closure. Essentially the same rule as any other closure-taking function that vends a pointer.