What is the correct way to reinterpret an entity i

2019-08-02 02:28发布

问题:

There are cases when you have to deal with a structure of certain type, but the upstream API requires you to present it though a pointer to another type at other places.

For instance, Unix Bind is expecting its second argument to be a pointer to a sockaddr, whereas the constructor should be sockaddr_in.

For now I'm sticking to a two-layer with*:

var sa = sockaddr_in(/* ... */)

withUnsafePointer(to: &sa) { _sa in
  _sa.withMemoryRebound(to: sockaddr.self, capacity: 1) { __sa in
    let err = bind(fd, __sa, socklen_t(saSize))
    precondition(err == 0)
  }
}

However, I'm discouraged by noisiness of this approach. When I use unsafeBitCast between pointer types:

bind(fd, unsafeBitCast(__sa, to: UnsafeMutablePointer<sockaddr>.self), socklen_t(saSize))

Then the compiler warns me not to do so, and recommends resort to withMemoryRebound.

When I use an in-place constructed pointer:

UnsafeMutablePointer(mutating: &sa).withMemoryRebound(to: sockaddr.self, capacity: 1) { _sa in
  let err = bind(fd, _sa, socklen_t(saSize))
  precondition(err == 0)
}

Then it works as well as the initial version, and rids us of one level of nesting. Although it looks more frail than with*. Also it's unclear, if the in-place pointer is the correct approach, why withUnsafePointer even exists.

Having that said, what is the canonical way to reinterpret a structure in Swift?

回答1:

Your first method is the correct one, using withUnsafePointer() and withMemoryRebound(), as one can see from the various examples provided in UnsafeRawPointer Migration, e.g.

In Swift 3, the user should explicitly rebind memory to a different type:

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))
  }
}

The other approach

UnsafeMutablePointer(mutating: &sa).withMemoryRebound(...) { ... }

looks fragile to me. sa is passed as an inout parameter to the constructor of UnsafeMutablePointer(), but that may be the address of temporary storage, and there is no guarantee that it is still valid when the constructor has returned and the closure is called.