-
Notifications
You must be signed in to change notification settings - Fork 30
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Asynchronous functions / callback support #31
Comments
For some extra context, I implemented a workaround in one of my own crates for calling methods with closures, but it's messy and not ideal: @_cdecl("my_func")
func myFunc() -> String {
let semaphore = DispatchSemaphore(value: 0)
var returnValue = ""
// getStringValue is not real, but has a signature something like this:
// func getStringValue(closure: @escaping (String) -> Void)
getStringValue() { (value) in
returnValue = value
semaphore.signal()
}
semaphore.wait()
return value
} Being able to use closures in Rust directly, or even better, do some magic to allow asynchronous functions, would be better than this approach. |
I think you'd be surprised 😉
type PluginMessageCallbackFn = unsafe extern "C" fn(c_int, c_int, *const c_char);
pub struct PluginMessageCallback(pub PluginMessageCallbackFn);
impl<'a> SwiftArg<'a> for PluginMessageCallback {
type ArgType = PluginMessageCallbackFn;
unsafe fn as_arg(&'a self) -> Self::ArgType {
self.0
}
} I could even try an Duplicate of #29, but I'll keep this open since it's more detailed. |
That would be amazing, I would love to contribute but this level of Rust FFI is out of my league :P |
While I'm in the area, braindump on this. The "right way" to call an async function from Rust is: //consider an async function with arg and return types
func foo(arg: Int) async -> String {
arg.description
}
//we need a function signature that works in the C ABI
@_cdecl("fooExported")
public func fooExported(arg: Int, completion: @Sendable @convention(c)(String) -> ()) {
/*
In general, async functions need to be called from a Swift executor.
This arbitrary Rust thread is probably not on one, hence the use of Task. As a corollary
we can't return a value to caller in the normal C way.
The impulse to solve via semaphore is well-intentioned, however Swift's design is such that ye arbitrary
Swift code is probably running on an executor thread, and if you block an executor thread your program may deadlock. Therefore a pattern guaranteed to not deadlock is usually desired.
*/
Task {
let r = await foo(arg: 2)
completion(r)
}
} This then raises the question about how to implement the //consider an async function with arg and return types
func foo(arg: Int) async -> String {
arg.description
}
//we need a function signature that works in the C ABI
@_cdecl("fooExported")
public func fooExported(arg: Int, context: UInt64, completion: @Sendable @convention(c)(SRString,UInt64) -> ()) {
Task {
let r = await foo(arg: 2)
completion(SRString(r), context)
}
} On Swift side, context is an opaque value. On Rust side, we can choose some value to smuggle in via Anyway, what value do we choose to smuggle? I use the continue crate for this. The key idea is async fn foo(arg: i64) -> String {
let (sender,receiver) = continuation();
let boxed_sender = Box::new(sender);
unsafe {
fooExported(1337, Box::into_raw(boxed_sender) as u64, completion)
}
receiver.await
}
extern "C" fn completion(string: SRString, context: u64) {
let unbox = unsafe {Box::from_raw(context as *mut Sender<String>)};
unbox.send(string.to_string());
} Ideally, I'd like to improve this boilerplate in some way by contributing some gadget to this crate. But it is not obvious to me how to design a gadget that works well:
It may be the best way to do this is the kind of manually-written pattern here. |
It would be amazing if we could call asynchronous Swift functions from Rust.
The only problem is:
_@cdecl
doesn't allow us to mark our functions as asynchronous:src-swift/lib.swift
Since that's not possible, it would be nice if we could have better support for closures:
src-swift/lib.swift
src/main.rs
The Rust code above is just a proof-of-concept, and I'm not sure if everything I described there is possible, since we can't just pass a pointer to the callback around (from my understanding at least). We may have to implement something like this C-callbacks example from the Rustonomicon (with some sort of Swift code generator?)
The text was updated successfully, but these errors were encountered: