convert tuple to swift array

I have a C struct that stores a MAC address as an array of 6 unsigned characters (u_char) i.e.


u_char mac[6];


In swift this array is seen as a tuple (UInt8,UInt8,UInt8,UInt8,UInt8,UInt8)


I want to store this tuple into a Swift array in the fastest way possible. Currently I am using memcpy but wondered if there is a more Swiftified way of doing this without sacrificing speed.


  var dst_mac: [u_char] {
       var a = [u_char](repeating: 0, count: 6)
       var mac = m_flow.dst_mac
       memcpy(&a, &mac, 6)
       return a
  }
Answered by DTS Engineer in 699159022

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"

Accepted Answer

[I’m no longer happy with this response. I’ve posted an update to it below.]

I usually do this via UnsafeBufferPointer. For example:

struct Foo {
    var tuple: (UInt8, UInt8, UInt8, UInt8, UInt8, UInt8)
    var array: [UInt8] {
        var tmp = self.tuple
        return [UInt8](UnsafeBufferPointer(start: &tmp.0, count: MemoryLayout.size(ofValue: tmp)))
    }
}

let f = Foo(tuple: (1, 2, 3, 4, 5, 6))
print(f.array)  // prints: [1, 2, 3, 4, 5, 6]

I think that code is clearer. Whether it’s faster is hard to say. You’d have to profile it, which is tricky when you’re dealing with tiny code snippets like this.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Thank you Quinn


Just I what I was looking for. I'm not convinced about clearer (too many years of C/C++ and memxxx have always been my friends) but I was concerned about my assumptions involving the number of bytes in the tuple and its alignment.


Bryan

I was concerned about my assumptions involving the number of bytes in the tuple and its alignment.

Yeah, I was concerned about that too. A while back I talked this over with one of the Swift engineers, and they assured me that homogeneous tuple elements would be laid out in a way that’s compatible with

UnsafeBufferPointer
, and thus the
&tmp.0
trick that I used on line 5 is fine.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Check out this source code from Apple: https://github.com/apple/swift-corelibs-foundation/blob/master/Foundation/UUID.swift


There are a bunch of different access patterns illustrated in here. One that I hadn't know about was `withUnsafePointer(to:)`.


-Wil

I've adjusted the original answer to make it more generic and also to support the other element types on top of UInt8.

extension Array {
  static func from(tuple: Any, start: UnsafePointer<Element>) -> [Element] {
    [Element](
      UnsafeBufferPointer(
        start: start,
        count: MemoryLayout.size(ofValue: tuple)/MemoryLayout<Element>.size
      )
    )
  }
}

var someTuple: (Double, Double, Double) = (1, 2, 3)
let array: [Double] = .from(tuple: someTuple, start: &someTuple.0)

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"

convert tuple to swift array
 
 
Q