

Recipe for Calling Swift Closures from Asynchronous Rust Code
source link: https://www.nickwilcox.com/blog/recipe_swift_rust_callback/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Recipe for Calling Swift Closures from Asynchronous Rust Code
The purpose of this recipe is to integrate asynchronous Rust with idiomatic Swift code that uses completion handler blocks. All reference counting should be handled correctly without having the Swift application code manually manage memory.
An example usage would be writing your iOS UI using Swift and running a long operation in Rust without blocking the UI thread, but still being able to notifying the user when the operation is complete.
PrerequisitesPermalink
This recipe assumes a basic understanding of mixing Rust and Swift code. See this tutorial for an introduction to the concepts.
Rust ImplementationPermalink
Let’s start on the Rust side and build a C ABI compatible representation of our asynchronous completion callback.
#[repr(C)]
pub struct CompletedCallback {
userdata: *mut c_void,
callback: extern "C" fn(*mut c_void, bool),
}
unsafe impl Send for CompletedCallback {}
impl CompletedCallback {
pub fn succeeded(self) {
(self.callback)(self.userdata, true);
std::mem::forget(self)
}
pub fn failed(self) {
(self.callback)(self.userdata, false);
std::mem::forget(self)
}
}
impl Drop for CompletedCallback {
fn drop(&mut self) {
panic!("CompletedCallback must have explicit succeeded or failed call")
}
}
From the code we can see the types has the requirement that succeeded
or failed
must be called, and that it can only be used once.
Nothing about the type is actually Swift specific. This type can be used in any language capable of FFI via C ABI.
Next is a quick example of an asynchronous function callable via C ABI.
#[no_mangle]
pub extern "C" fn async_operation(callback: CompletedCallback) {
thread::spawn(move || {
thread::sleep(Duration::from_secs(3));
callback.succeeded()
});
}
Swift ImplementationPermalink
As we move to the Swift calling side we’ll need to create a bridging header so that Swift recognizes our types and function.
#ifndef BridgingHeader_h
#define BridgingHeader_h
#import <Foundation/Foundation.h>
typedef struct CompletedCallback {
void * _Nonnull userdata;
void (* _Nonnull callback)(void * _Nonnull, bool);
} CompletedCallback;
void async_operation(CompletedCallback callback);
#endif
Then we need to wrap the imported function with function that presents an idiomatic interface. This is we’re we’ll map to our Rust types and handle manual reference counting.
private class WrapClosure<T> {
fileprivate let closure: T
init(closure: T) {
self.closure = closure
}
}
public func FriendlyAsyncOperation(closure: @escaping (Bool) -> Void) {
// step 1
let wrappedClosure = WrapClosure(closure: closure)
let userdata = Unmanaged.passRetained(wrappedClosure).toOpaque()
// step 2
let callback: @convention(c) (UnsafeMutableRawPointer, Bool) -> Void = { (_ userdata: UnsafeMutableRawPointer, _ success: Bool) in
let wrappedClosure: WrapClosure<(Bool) -> Void> = Unmanaged.fromOpaque(userdata).takeRetainedValue()
wrappedClosure.closure(success)
}
// step 3
let completion = CompletedCallback(userdata: userdata, callback: callback)
//step 4
async_operation(completion)
}
Step 1Permalink
We need to take our Swift closure and turn it into a void *
so it can be the userdata
member. Unmanaged.passRetained
will manually increment the reference count and give us an unmanaged object which can be cast using toOpaque
. Unfortunately in Swift closures are not something we can manually retain, so we need to make it a property of the WrapClosure
type.
Step 2Permalink
We create a C compatible function pointer to an inner helper closure. This closure has a signature that matches our C api: (void * _Nonnull userdata, bool success)
. In this helper closure we reverse step 1 and manually decrement the reference count on the closure and turn it back to a Swift type. Because we are back to letting Swift manage our reference count, when the scope ends the wrapped closure will be freed.
Step 3Permalink
We can initialize all the members of the C callback structure without the calling code having to worry about @convention(c)
closures and UnsafeMutableRawPointer
Step 4Permalink
Invoke the Rust function
Example Swift CallerPermalink
We’ll demonstrate the recipe with a Swift CLI application:
class TestLifetime {
let sema: DispatchSemaphore
init(_ sema: DispatchSemaphore) {
self.sema = sema
print("start of test lifetime")
}
deinit {
print("end of test lifetime")
}
func completed(_ success: Bool) {
print("the async operation has completed with result \(success)")
sema.signal()
}
}
func startOperation(_ sema: DispatchSemaphore) {
let test = TestLifetime(sema)
print("starting async operation")
FriendlyAsyncOperation() { [test] success in
test.completed(success)
}
}
let semaphore = DispatchSemaphore(value: 0)
startOperation(semaphore)
semaphore.wait()
Invoking FriendlyAsyncOperation()
looks just like any other asynchronous Swift function.
If we run the above code we get the following output
start of test lifetime
starting async operation
the async operation has completed with result true
end of test lifetime
The instance of TestLifetime
has been captured and it’s lifetime is extended beyond the scope of the startOperation
function and lasts until the asynchronous operation in Rust has completed.
CodePermalink
All the code can be found on Github
Recommend
-
34
Swift Closures and Firebase – Handling A...
-
111
Both Swift and Go are modern compiled languages. They have their own typical use cases, strengths a...
-
15
The Easy Case: Lua Since hard is always relative to something else, I’d like to start with a dynamic language. Functions in Lua are essentially anonymous and can capture variables f...
-
17
Problem setup Recently I've had to write some nom code (it is a parser co...
-
4
Rust closures as input parameters 5 minute read 23 May 2016 Edit:
-
13
David Cordero Swift: Avoid auto return in closures Published on 06 Dec 2014 In Swif...
-
12
05 Apr 2021 Updated: 06 Apr 2021 software-dev ...
-
12
10 Jun 2018 software-dev
-
7
What is @escaping in Swift closures 19 Oct 2020 ⋅ 5 min read ⋅ Swift
-
9
Why should we avoid using closures in Swift structs? Jan 10, 2020 • Rizwan •...
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK