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?
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)?
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?
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.