Dalija Prasnikar Dalija Prasnikar - 1 month ago 19
Swift Question

Capturing values in nested closures

What is proper syntax for using captured value in nested closure?

I have following working code for calculating CRC32 from integer value using

zlib
library.

func testCrc()
{
var x: UInt32 = 0xffffffff

let result = withUnsafePointer(to: &x, {
$0.withMemoryRebound(to: Bytef.self, capacity: 4) {
crc32(0, $0, 4)
}
})

XCTAssertEqual(0xffffffff, result)
}


I want to create standalone generic function that can calculate CRC32 from any value. In order to do that, besides the value itself I have to also calculate and pass it's size, so I cannot use explicit size - 4 - like I used in above code.

But I am having trouble finding correct syntax to pass calculated size to inner closure.

func calculateCrc32<T>(_ crc: UInt, value: inout T) -> UInt
{
let size = MemoryLayout.size(ofValue: value)
let result = withUnsafePointer(to: &value, {
$0.withMemoryRebound(to: Bytef.self, capacity: size) {
crc32(crc, $0, size) // error
}
})
return result
}


Above code shows rather confusing compiler error for parameter
$0
:


Cannot convert value of type 'UnsafeMutablePointer<_>' to expected
argument type 'UnsafePointer!'


Confusing, because if I replace
crc32(crc, $0, size)
with
crc32(crc, $0, 4)
compiler is not complaining and function works properly for values with size of 4 bytes.

How to resolve above issue?

Answer

The error message is misleading. Your code is almost correct, the "only" problem is the last argument of crc32() which needs to be an uInt:

func calculateCrc32<T>(_ crc: UInt, value: inout T) -> UInt
{
    let size = MemoryLayout.size(ofValue: value)
    let result = withUnsafePointer(to: &value, {
        $0.withMemoryRebound(to: Bytef.self, capacity: size) {
            crc32(crc, $0, uInt(size))
        }
    })
    return result
}

If you call crc32(crc, $0, 4) then the "integer literal" 4 is passed as the last argument, and the compiler infers its type as uInt to match the function definition.

It does not compile with crc32(crc, $0, size) because Swift does not implicitly convert between types.

Alternatively, use numericCast(), which is a generic function which converts between different signed and unsigned integer types (and traps on overflow).

I would also suggest to make a local variable copy of the value instead of using an inout parameter, that makes it easier to call the function:

func calculateCrc32<T>(_ crc: UInt, value: T) -> UInt {
    var value = value
    let size = MemoryLayout.size(ofValue: value)
    let result = withUnsafePointer(to: &value, {
        $0.withMemoryRebound(to: Bytef.self, capacity: size) {
            crc32(crc, $0, numericCast(size))
        }
    })
    return result
}
Comments