@Karl withMemoryRebound requires that the memory capacity not change because it doesn't do the math on MemoryLayout.stride to figure out the ratio. You can do that manually with bindMemory but using that API correctly is even harder.
As for
ptr.assumingMemoryBound(to: UInt8.self).pointee += 1
...don't do this. The safe thing is:
ptr.storeBytes(of: ptr.load(as: UInt8.self) + 1, as: UInt8.self)
assumingMemoryBound was supposed to be so ugly that no one would use it, but the safe thing is still uglier.
I don't want to make this post entirely about memory binding, since the OP makes a very good point without delving into that, but since it came up ;)
Here's a summary of the terrible three memory binding APIs. At this point, they serve as a minimal formalization of the model for circumventing Swift pointer aliasing rules. Swift pointers are strict primarily because, to be safe in the presence of C code, they need to more strict than C, and adding special cases to the language (to mimic C) I believe just encourages more rule breaking. Finding undefined behavior related to pointer types in a Swift project boils down to grepping for any of these APIs, which is actually much better than the likely alternative in which implicit casting could induce undefined behavior.
The usability and egonomics just aren't there, partly because
- Higher priorities
- With the exception of calling few legacy C APIs (pthread/socket) well-designed code doesn't use type punning
- It's hard to motivate designing a feature that we don't want anyone to use.
- We need to see what people really want before designing it
I do think it will be much easier for users to know what to reach for when we introduce something like ReinterpretedPointer.
assumingMemoryBound(to:)
I consider any Swift code calling assumingMemoryBound to be unsafe unless:
- It is accompanied by a comment explaining why the assumption holds in this particular case.
- That logic can be easily confirmed by locally reasoning about the code.
I know of two expected use cases for assumingMemoryBound:
-
Writing a wrapper around a C API taking a void * callback such as pthread_create.
-
Passing a Swift Unsafe[Mutable]RawPointer to a C API taking char * or unsigned char *.
In both cases, the typed pointer produced by assumingMemoryBound(to:) should be passed directly to the imported C code without accessing it in Swift. There's always a danger that the imported C function could by replaced by a Swift shim. However, I don't know how to avoid assumingMemoryBound(to:) in these cases without special support in the Swift type system, which is a fairly high bar.
Within the standard library, there are legitimate uses of assumingMemoryBound in which a raw buffer's bound type can be dynamically determined with some tag or discrimiator bit. I did't count that as an "expected" use case though. In the future, I think it would be better to do this sort of thing with a ReinterpretedPointer<T> type.
withMemoryRebound(to:capacity:)
withMemoryRebound(to:capacity:) is safer than assumingMemoryBound, but also only intended for C interop when two distinct imported C type are known to have a compatible layout. The canonical example of this from the original Swift 3 migration guide is:
var addr = sockaddr_in()
let sock = socket(PF_INET, SOCK_STREAM, 0)
let result = withUnsafePointer(to: &addr) {
// Temporarily bind the memory at &addr to a single instance of type sockaddr.
$0.withMemoryRebound(to: sockaddr.self, capacity: 1) {
connect(sock, $0, socklen_t(MemoryLayout<sockaddr_in>.stride))
}
}
bindMemory(to:capacity)
bindMemory(to:capacity) is the only of the three meant for use in pure Swift. It allows Swift code to decouple memory allocation from knowledge of the type that memory will hold. So, it's useful for layering a raw memory allocator, or raw byte buffer underneath a strictly typed view of the buffer.
In a recent thread about SwiftNIO, an unanticipated need for bindMemory(to:capacity:) for C interop came up. The C struct z_stream exposes a typed pointer into a raw byte buffer.
struct z_stream {
unsigned char *next_in;
//...
}
zstream.next_in = dataPtr.bindMemory(to: UInt8.self, capacity: count)
I'm not sure how else to get around this without defining your own layout compatible z_stream struct in C that takes void *. Then you would need to deal with the additional type mismatch when calling zlib.
Another way to deal with this would be adding some type system support for importing [unsigned] char * "differently" in select cases.