Can an UnsafeBufferPointer to local scope Array outlive it's own reference?

I'm working on a binding library, and I have this code:

public var parameters: [D3DRootParameter] {
    set {
        let values = newValue.map({$0.rawValue})
        values.withUnsafeBufferPointer {
            self.rawValue.pParameters = $0.baseAddress
        }
        self.rawValue.NumParameters = UInt32(newValue.count)
    }
}

It doesn't work because values doesn't outlive the scope (this is a property setter).
My current solution is to keep the transformed array alive like so:

public var parameters: [D3DRootParameter] {
    set {
        self.parametersRef = newValue.map({$0.rawValue})
        self.parametersRef.withUnsafeBufferPointer {
            self.rawValue.pParameters = $0.baseAddress
        }
        self.rawValue.NumParameters = UInt32(newValue.count)
    }
}
private var parametersRef: [D3DRootParameter.RawValue]! = nil 

I was wondering if there was a Swift approved way to solve this problem while keeping it in the setter scope?

Also note, I modified the examples to make them forum friendly. There is a getter in the actual code.

the easiest way is to allocate the buffer explicitly, and copy the array elements into the buffer, using initialize(from:) or one of its many variants.

a slightly more performant way is to retain the Array with Unmanaged<T>, so it won’t get deinitialized when the withUnsafeBufferPointer closure exits. you have to box it as AnyObject, since Array is a struct wrapping its managed storage. this is the standard “swift approved way” of temporarily passing ownership of something to a C API. but of course, you have to stash the opaque pointer (not the buffer base address pointer!) somewhere and remember to release the Array later manually so it won’t leak. it goes without saying that you must not modify the Array while a C API is holding a pointer to its storage, but you have already lost access to it on leaving the setter, so this probably won’t be an issue.

keep in mind the C API does not know how to manage the elements in the passed buffer. if it tries to assign a value to one of the buffer elements, the existing element at that position will leak.

3 Likes

Thank you. The Array as AnyObject for Unmanaged was helpful, I didn't know you could do that. After experimenting it appears I can't use this method and maintain a simple Swift experience for the packages users. If it were a single array a retain would be fine, becuase I can use deinit to manage it, but unfortunately it's pointers to pointers to pointers. I can't guarantee the retains will get released; a user might never pass the struct to anything that consumes it. Requiring users to release things themselves defeats the purpose of the binding package. I want it to be Swifty.

So I think I'm going to extend the withUnsafe style to my wrappers by making a withUnsafeRawValue function that constructs all the C structs, and will be called by other functions that consume the wrapper. So maintaining an underlying C struct inside the wrapper was the wrong approach. Someone go back 3 months and tell me :stuck_out_tongue_winking_eye:

It'll be a ton of extra work and I'll have to deal with several massive waterfalls of closures, but I think it's the safest way to do this.

This is not acceptable and you cannot rely on it working. Array is very clear on the lifetime of withUnsafeBufferPointer:

  /// The pointer passed as an argument to `body` is valid only during the
  /// execution of `withUnsafeBufferPointer(_:)`. Do not store or return the
  /// pointer for later use.

This is not ambiguous. It does not imply that keeping the array reference alive will continue to keep this vended pointer alive. Indeed, in many cases that look very similar to this one this mechanism will not work as expected. It relies tightly on the order of operations as well. There are specific situations where this is workable, but in the general case this is not safe to do.

4 Likes

Extending the withUnsafe closure method, I ran into another issue.

I have an array of unknown length that contain elements I need to recursively use withUnsafe while storing each result and then passing them to the body which also needs to execute inside the final element.

This is the issue here. I'm not sure how to enumerate over this while executing the closures in a nested sequence. Any ideas?

parameters.map({$0.withUnsafeRawValue({$0})}).withUnsafeBufferPointer {

}

This is the entire function for reference.

internal func withUnsafeRawValue<ResultType>(_ body: (RawValue) throws -> ResultType) rethrows -> ResultType {
    return try parameters.map({$0.withUnsafeRawValue({$0})}).withUnsafeBufferPointer {
        let pParameters = $0.baseAddress
        let NumParameters = UInt32(parameters.count)
        return try staticSamplers.map({$0.rawValue}).withUnsafeBufferPointer {
            let pStaticSamplers = $0.baseAddress
            let NumStaticSamplers = UInt32(staticSamplers.count)
            
            let Flags = flags.rawType
            let rawValue = RawValue(NumParameters: NumParameters,
                                    pParameters: pParameters,
                                    NumStaticSamplers: NumStaticSamplers,
                                    pStaticSamplers: pStaticSamplers,
                                    Flags: Flags)
            return try body(rawValue)
        }
    }
}

This will not work: the map is doing exactly what it may not do, which is escaping the pointer outside of the enclosing with block.

There are only two options: either you have to heap-allocate storage to flatten things out (e.g. using UnsafeMutableBufferPointer) or you need to recursively produce the pointers. Either can work. Recursion risks busting the stack if the compiler cannot eliminate the calls (which it will likely fail to do), and heap allocation is unpleasant, but these are your only real choices.

Yup I know that won't work, just a 1 line example. What I want to do is this:

parameters[0].withUnsafeRawValue {p0 in
    parameters[1].withUnsafeRawValue {p1 in 
        let rawParameters = [p0, p1]
        // making the raw value...
        body(rawValue)
    }
}

But recursively because the array is dynamic.
I think I'm over thinking this because it's closures but it should be the same as recursive function calling.

Thanks for the suggestions! I'll try doing recursion tomorrow. The array is meant to be single digit length so I'm not worried about it getting out of control.

As an example of how you might do this recursively, I have thrown together a solution to the common problem: "How do I take [String] and turn it into [UnsafePointer<CChar>]?" that uses recursion. Hopefully you'll be able to see how it would translate:

extension Array where Element == String {
    func withArrayOfCStrings<ResultType>(_ block: ([UnsafePointer<CChar>]) throws -> ResultType) rethrows -> ResultType {
        func pointerGenerator(strings: inout ArraySlice<String>, cStrings: inout [UnsafePointer<CChar>], block: ([UnsafePointer<CChar>]) throws -> ResultType) rethrows -> ResultType {
            if let nextString = strings.popFirst() {
                return try nextString.withCString { cString in
                    cStrings.append(cString)
                    return try pointerGenerator(strings: &strings, cStrings: &cStrings, block: block)
                }
            } else {
                return try block(cStrings)
            }
        }

        var stringSlice = self[...]
        var cStrings = Array<UnsafePointer<CChar>>()
        cStrings.reserveCapacity(stringSlice.count)
        return try pointerGenerator(strings: &stringSlice, cStrings: &cStrings, block: block)
    }
}
3 Likes

Oh awesome! That's exactly what I was envisioning in my head.

Bear in mind that this has the same limitation as withCString or Array's withUnsafeBufferPointer - the Array you are provided in the closure must not escape that closure, as its elements are a bunch of temporary pointers.

Personally I think that's pretty dubious with a type like Array, even if the element type is an UnsafePointer, but it's a matter of individual taste. At the very least, I'd call the method withArrayOfUnsafeCStrings.

This is the solution I ended up with:

internal func withUnsafeRawValue<ResultType>(_ body: (RawValue) throws -> ResultType) rethrows -> ResultType {
    func withUnsafeParameter(at index: Int, _ pParameters: inout [D3DRootParameter.RawValue], _ body: (RawValue) throws -> ResultType) rethrows -> ResultType {
        if parameters.indices.isEmpty || index == parameters.indices.last! + 1 {
            return try pParameters.withUnsafeBufferPointer {
                let pParameters = $0.baseAddress
                let NumParameters = UInt32(parameters.count)
                return try staticSamplers.map({$0.rawValue}).withUnsafeBufferPointer {
                    let pStaticSamplers = $0.baseAddress
                    let NumStaticSamplers = UInt32(staticSamplers.count)
                    let Flags = flags.rawType
                    let rawValue = RawValue(NumParameters: NumParameters,
                                            pParameters: pParameters,
                                            NumStaticSamplers: NumStaticSamplers,
                                            pStaticSamplers: pStaticSamplers,
                                            Flags: Flags)
                    return try body(rawValue)
                }
            }
        }

        return try parameters[index].withUnsafeRawValue {
            pParameters.insert($0, at: index)
            return try withUnsafeParameter(at: index + 1, &pParameters, body)
        }
    }

    var pParameters: [D3DRootParameter.RawValue] = []
    pParameters.reserveCapacity(parameters.count)
    return try withUnsafeParameter(at: 0, &pParameters, body)
}

It's a little gross but it gets the job done, safely.

I've adapted your code to provide a BufferPointer rather than an array, since I face a lot of C-API where the a character count and the baseAddress of the C-Array is required and I thought this might help other people in the future:

extension Array where Element == String {
    @inlinable
    public func withUnsafeCStringBufferPointer<R>(_ body: (UnsafeBufferPointer<UnsafePointer<CChar>?>) throws -> R) rethrows -> R {
        func translate(_ slice: inout Self.SubSequence,
                       _ offset: inout Int,
                       _ buffer: UnsafeMutableBufferPointer<UnsafePointer<CChar>?>,
                       _ body: (UnsafeBufferPointer<UnsafePointer<CChar>?>) throws -> R) rethrows -> R {
            guard let string = slice.popFirst() else {
                return try body(UnsafeBufferPointer(buffer))
            }
            
            return try string.withCString { cStringPtr in
                buffer.baseAddress!
                    .advanced(by: offset)
                    .initialize(to: cStringPtr)
                offset += 1
                return try translate(&slice, &offset, buffer, body)
            }
        }
        
        var slice = self[...]
        var offset: Int = 0
        let buffer = UnsafeMutableBufferPointer<UnsafePointer<CChar>?>.allocate(capacity: count)
        defer { buffer.deallocate() }
        return try translate(&slice, &offset, buffer, body)
    }
}

https://gist.github.com/ctreffs/788122b5f53f6fd6758f3f89e3e263a6

The code seems to work fine but I was wondering if I might be doing it wrong to deallocate the buffer pointer here or if anything else might be wrong?

This code seems fine to me, deallocating the buffer pointer where you do is entirely correct.

1 Like

Thanks for the confirmation :slight_smile:

Terms of Service

Privacy Policy

Cookie Policy