Assigning to self in defer block produces error

I tried to write the following code today, which compiles and seems to execute correctly. But I initially tried to use a defer block to clean it up a bit. Here’s the good code:

extension
Data
{
    init(unsafeUninitializedCapacity inCapacity: Int,
            initializingWith initializer: (inout UnsafeMutableRawBufferPointer, inout Int) throws -> Void)
        rethrows
    {
        let buffer = UnsafeMutableRawPointer.allocate(byteCount: inCapacity, alignment: MemoryLayout<UInt8>.alignment)
        var bp = UnsafeMutableRawBufferPointer(start: buffer, count: inCapacity)
        let originalAddress = bp.baseAddress
        var count: Int = 0
        defer
        {
            precondition(count <= inCapacity, "Initialized count set to greater than specified capacity.")
            precondition(bp.baseAddress == originalAddress, "Can't reassign buffer in Array(unsafeUninitializedCapacity:initializingWith:)")
        }
        
        do
        {
            try initializer(&bp, &count)
        }
        
        catch (let e)
        {
            //  Ensure the buffer gets deallocated no matter what. Might be better to just deallocate it directly
            self = Data(bytesNoCopy: buffer, count: count, deallocator: .custom({ b,c in b.deallocate() }))
            throw e
        }
        
        self = Data(bytesNoCopy: buffer, count: count, deallocator: .custom({ b,c in b.deallocate() }))
    }
}

and here’s the code the compiler didn’t like:

extension
Data
{
    init(unsafeUninitializedCapacity inCapacity: Int,
            initializingWith initializer: (inout UnsafeMutableRawBufferPointer, inout Int) throws -> Void)
        rethrows
    {
        let buffer = UnsafeMutableRawPointer.allocate(byteCount: inCapacity, alignment: MemoryLayout<UInt8>.alignment)
        var bp = UnsafeMutableRawBufferPointer(start: buffer, count: inCapacity)
        let originalAddress = bp.baseAddress
        var count: Int = 0
        defer
        {  <----  'self' used before 'self.init' call or assignment to 'self'
            precondition(count <= inCapacity, "Initialized count set to greater than specified capacity.")
            precondition(bp.baseAddress == originalAddress, "Can't reassign buffer in Array(unsafeUninitializedCapacity:initializingWith:)")
            self = Data(bytesNoCopy: buffer, count: count, deallocator: .custom({ b,c in b.deallocate() }))
        }
        
        do
        {
            try initializer(&bp, &count)
        }
        
        catch (let e)
        {
            throw e
        }
    }  <----  'self.init' isn't called on all paths before returning from initializer
}

Neither message is entirely clear to me.

IIRC, the compiler currently implements defer blocks by desugaring into a closure which is automatically called. This is why the error text is as it is.
I am uncertain if this case is intended to error in the case of defer blocks, it likely hasn't been specifically considered.

The defer block is called after the function returns. So you haven't initialized your type within the initializer, which is the problem.

Note the "before" in the error message 'self.init' isn't called on all paths before returning from initializer

Terms of Service

Privacy Policy

Cookie Policy