I’m not happy with the code I posted earlier. The fundamental problem is this snippet:
UnsafeBufferPointer(start: &tmp.0, …)
There’s actually a couple of issues:
-
The tmp.0
pointer generated by the &
is only valid for the duration of the UnsafeBufferPointer.init(start:count:)
initialiser, which means it could be invalid at the point that the [UInt8].init(_:)
runs. In practice this doesn’t happen, but it could.
-
Using tmp.0
only pins the first element of the tuple, not the whole tuple.
For more background on this overall issue, see The Peril of the Ampersand.
A better option is this:
struct Foo {
var tuple: (UInt8, UInt8, UInt8, UInt8, UInt8, UInt8)
var array: [UInt8] {
withUnsafeBytes(of: self.tuple) { buf in
[UInt8](buf)
}
}
}
This bounces through a UnsafeRawBufferPointer
which is convenient and safe in this case, where the tuple elements are just bytes. This is also the most common case, because you usually see this issue when dealing with fixed-sized arrays of char
imported from C.
If you’re dealing with some other type you need more complex code. For example:
struct Foo {
var tuple: (CInt, CInt, CInt, CInt, CInt, CInt)
var array: [CInt] {
return withUnsafePointer(to: self.tuple) { tuplePtr in
let start = tuplePtr.qpointer(to: \.0)!
let count = MemoryLayout.size(ofValue: tuplePtr.pointee) / MemoryLayout.size(ofValue: tuplePtr.pointee.0)
let buf = UnsafeBufferPointer(start: start, count: count)
return [CInt](buf)
}
}
}
Here the withUnsafePointer(…)
call pins the entire tuple at a fixed location in memory, which allows you to safely get a pointer to one of its elements.
Note that the qpointer(…)
method is my version of the helper method that we’re adding as part of SE-0334 Pointer API Usability Improvements. Here’s an implementation you can use today:
extension UnsafePointer {
public func qpointer<Property>(to property: KeyPath<Pointee, Property>) -> UnsafePointer<Property>? {
guard let offset = MemoryLayout<Pointee>.offset(of: property) else { return nil }
return (UnsafeRawPointer(self) + offset).assumingMemoryBound(to: Property.self)
}
}
The take-home lesson here: Pointer manipulation is tricky, even in Swift.
Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"