When you write:
let incorrectTuple2 = withUnsafePointer(to: &aCStructWithATuple.tuple.0) { $0[2] }
You are accessing outside the bounds of the pointer, so it is undefined behaviour. In fact, I think this pattern is always UB, even for non-tuple values:
// Always UB. For any type.
withUnsafePointer(to: &x) { $0[2] }
If you want a pointer which covers all elements of the tuple, you must pass the entire tuple as the argument. Indeed, that means you will need to cast the pointer - and that is safe, but only if the tuple is homogenous (all elements have the same type). A pointer to a homogenous tuple is already bound to its element type, so it is appropriate to use assumingMemoryBound (as you have done).
This is something we really do need to improve; probably with some sort of real fixed-size arrays. For now, the best I can suggest is to copy and paste a bunch of helper functions for the tuple sizes you need.
// Arity 8:
@inlinable @inline(__always)
internal func withUnsafeBufferPointerToElements<T, Result>(
tuple: (T, T, T, T, T, T, T, T), _ body: (UnsafeBufferPointer<T>) -> Result
) -> Result {
return withUnsafeBytes(of: tuple) {
let ptr = UnsafeBufferPointer(start: $0.baseAddress!.assumingMemoryBound(to: T.self), count: 8)
return body(ptr)
}
}
@inlinable @inline(__always)
internal func withUnsafeMutableBufferPointerToElements<T, Result>(
tuple: inout (T, T, T, T, T, T, T, T), _ body: (inout UnsafeMutableBufferPointer<T>) -> Result
) -> Result {
return withUnsafeMutableBytes(of: &tuple) {
var ptr = UnsafeMutableBufferPointer(start: $0.baseAddress!.assumingMemoryBound(to: T.self), count: 8)
return body(&ptr)
}
}