Unable to use nested withUnsafePointer(to:_:) with parameters marked `borrowing`

Hello, I’m having a go at writing a Vulkan game engine with Swift’s new and improved C/C++ interop. I’m trying to make a noncopyable wrapper struct for a VkInstance, but the following code does not compile:

struct Instance: ~Copyable {
   let inner: VkInstance

   init(createInfo: borrowing VkInstanceCreateInfo, allocator: borrowing VkAllocationCallbacks) throws (VulkanKitError) {
      var instance = VkInstance(bitPattern: 0)

      let res = withUnsafePointer(to: createInfo) { pCreateInfo in
         withUnsafePointer(to: allocator) { pAllocator in
            return vkCreateInstance(pCreateInfo, pAllocator, &instance)
         }

      }
      if res == VK_SUCCESS {
         inner = instance!
      } else {
         throw .instanceCreationFailed(res)
      }
   }

   deinit {
      vkDestroyInstance(inner, nil)
   }
}

with the error:
error: 'allocator' cannot be captured by an escaping closure since it is a borrowed parameter.

Is there any way to get around this without simply removing the parameter modifiers?

Wrap the call to withUnsafePointer in withoutActuallyEscaping?

Though it seems like you’d want to write wrapper types for all the Vulkan types so you don’t have to do this dance all over the place.

Hello, do you mean like this?

  let res = withoutActuallyEscaping(
     {
        withUnsafePointer(to: createInfo) { pCreateInfo in
           withUnsafePointer(to: allocator) { pAllocator in
              return vkCreateInstance(pCreateInfo, pAllocator, &instance)
           }
        }
     }
  ) {
     $0()
  }

I think you’d need to pass the allocator as an argument to the wrapped closure.

Unfortunately that just shunts the error to the createInfo parameter:

      let res = withoutActuallyEscaping(
         { (alloc: borrowing VkAllocationCallbacks) in
            withUnsafePointer(to: createInfo) { pCreateInfo in
               withUnsafePointer(to: allocator) { pAllocator in
                  return vkCreateInstance(pCreateInfo, pAllocator, &instance)
               }
            }
         }
      ) {
         $0(allocator)
      }

Yeah, you’ll probably need to make that a closure argument too. The ultimate goal is that none of your borrowing parameters are captured.

Also I think you need to update the inner use of allocator to alloc.

By the way, does it actually make sense to be using borrowing on your callbacks? Isn’t that just a bundle of function pointers, and therefore a value type? I can see an argument that allocator should only be borrowed for the duration of the call, but isn’t that also just the default semantic for function parameters?

Passing both as arguments just shifts the error down:

      let res = withoutActuallyEscaping(
         { (createInfo: borrowing VkInstanceCreateInfo, alloc: borrowing VkAllocationCallbacks) in
            withUnsafePointer(to: createInfo) { pCreateInfo in
               withUnsafePointer(to: alloc) { pAllocator in
                  return vkCreateInstance(pCreateInfo, pAllocator, &instance)
               }
            }
         }
      ) {
         $0(createInfo, allocator)
      }

and trying to use withoutActuallyEscaping(_:do:) within the first onen doesn’t work:

      let res = withoutActuallyEscaping(
         { (createI: borrowing VkInstanceCreateInfo, alloc: borrowing VkAllocationCallbacks) in
            withUnsafePointer(to: createI) { (pCreateInfo) in
               withoutActuallyEscaping(
                  { (a: borrowing VkAllocationCallbacks) in
                     withUnsafePointer(to: a) { pAllocator in
                        return vkCreateInstance(pCreateInfo, pAllocator, &instance)
                     }
                  }
               ) {
                  $0(alloc)
               }
            }
         }
      ) {
         $0(createInfo, allocator)
      }

By the way, does it actually make sense to be using borrowing on your callbacks?

I’m not sure, the Vulkan API takes both structs by pointer, I assume to make them optional, but also because they’re both pretty large structs, so passing by value (unless optimised out by the Swift compiler) could be expensive (could be wrong on this)?

**Although the VkInstanceCreateInfo parameter isn’t optional, so that is probably only because of the large struct issue.

the Vulkan API takes both structs by pointer

So I chose to use borrowing, assuming that it would make the Swift compiler more likely to not pass the whole thing by value.

I don’t think borrowing would have the effect you think it would, because I don’t think VkAllocationCallbacks is going to be imported as ~Copyable. So per Joe’s explanation, the borrowing keyword won’t have any effect on the calling convention since borrowing is already the default for function arguments.

Can you share these errors, please?

I did find a related thread from this summer where someone encountered the same issue with SDL, but there was no satisfying result.

Unfortunately, I don’t think you can use borrowing to force your desired calling convention without making the type non-copyable, which can only be done by creating your own ~Copyable wrapper type.

because I don’t think VkAllocationCallbacks is going to be imported as ~Copyable

I see.

Can you share these errors, please?

For the first one:

19 |          { (createInfo: borrowing VkInstanceCreateInfo, alloc: borrowing VkAllocationCallbacks) in
   |                                                         `- error: 'alloc' cannot be captured by an escaping closure since it is a borrowed parameter

And the second:

{ (createI: borrowing VkInstanceCreateInfo, alloc: borrowing VkAllocationCallbacks) in
   |                                                      `- error: 'alloc' cannot be captured by an escaping closure since it is a borrowed parameter

I don’t think you can use borrowing to force your desired calling convention without making the type non-copyable,

Alright, in that case I’ll skip the borrowing modifier for now, but I assume that I would continue having the same problems if I did create a ~Copyable wrapper type?

At least in pure Swift, marking the type as ~Copyable allows your initial formulation to compile:

struct Foo: ~Copyable { }

func f(_ arg1: borrowing Foo, _ arg2: borrowing Foo) {
    withUnsafePointer(to: arg1) { ptr1 in
        withUnsafePointer(to: arg2) { ptr2 in
            g(ptr1, ptr2)
        }
    }
}

func g(_ ptr1: UnsafePointer<Foo>, _ ptr2: UnsafePointer<Foo>) {
    print("\(ptr1), \(ptr2)")
}

If you remove the : ~Copyable from Foo, you get your original error:

/tmp/foo.swift:3:33: error: 'arg2' cannot be captured by an escaping closure since it is a borrowed parameter
 1 | struct Foo { }
 2 | 
 3 | func f(_ arg1: borrowing Foo, _ arg2: borrowing Foo) {
   |                                 `- error: 'arg2' cannot be captured by an escaping closure since it is a borrowed parameter
 4 |     withUnsafePointer(to: arg1) { ptr1 in
   |                                 `- note: closure capturing 'arg2' here
 5 |         withUnsafePointer(to: arg2) { ptr2 in
 6 |             g(ptr1, ptr2)

It’s unfortunate IMO that the result would be applying ~Copyable to your Swift projection of a value type, when your intent is merely to avoid spilling these structs to the stack. Making your wrapper type a class would be a more conventional way to avoid this problem, but then of course you wind up introducing reference semantics and atomic refcounting. The “missing feature” here is indirect struct, which would allow you to preserve value semantics but tell the compiler to pass values as pointers.

Interesting, thank you for the help, and good to know I won’t have the same issue if I write wrapper types.

It should work fine if you put the VkInstanceCreeateInfo into a thin non-copyable wrapper. This code compiles:

struct Unique<T: ~Copyable>: ~Copyable {
  public var value: T
  public init(_ value: consuming T) {
    self.value = value
  }
}

struct Foo { }

func f(_ arg1: borrowing Unique<Foo>, _ arg2: borrowing Unique<Foo>) {
    withUnsafePointer(to: arg1.value) { ptr1 in
        withUnsafePointer(to: arg2.value) { ptr2 in
            g(ptr1, ptr2)
        }
    }
}

func g(_ ptr1: UnsafePointer<Foo>, _ ptr2: UnsafePointer<Foo>) {
    print("\(ptr1), \(ptr2)")
}
1 Like