Dalija Prasnikar
Dalija Prasnikar

Reputation: 28516

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?

Upvotes: 4

Views: 190

Answers (1)

Martin R
Martin R

Reputation: 539745

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
}

Upvotes: 4

Related Questions