swift-bridge 0.1.37 supports passing `Box<dyn FnOnce(A, B) -> C>` from Rust to Swift
As of the latest release of swift-bridge is is now possible to pass boxed functions from Rust -> Swift. The boxed function shows up on the Swift side as Swift's native function type, and gets de-allocated after it is called or dropped on the Swift side.
https://github.com/chinedufn/swift-bridge/pull/110
Boxed function support is useful for people writing applications on Apple hardware that need their Rust code to be able to interface with callback-based asynchronous Swift code.
One example is if you're building a Rust application on iOS that needs to interact with the Stripe Terminal SDK's Swift APIs.
Before swift-bridge 0.1.37 you'd need to define opaque Rust types such as CollectCardPaymentCallback(Box<dyn FnOnce(Result<PaymentIntent, String>)>) in order to send over a callback that the Swift side that could be called.
This was cumbersome since you needed to bridge a type for every callback that your application needed.
Now, instead, you can do something like:
#[swift_bridge::bridge]
mod ffi {
extern "Swift" {
type StripeTerminalBridgedSingleton;
#[swift_bridge(init)]
fn new() -> StripeTerminalBridgedSingleton;
fn retrieve_payment_intent(
&self,
client_secret: &str,
callback: Box<dyn FnOnce(PaymentIntentWrapper, String)>,
);
fn collect_payment_method(
&self,
payment_intent: PaymentIntentWrapper,
callback: Box<dyn FnOnce(PaymentIntentWrapper, String)>,
);
fn process_payment(
&self,
payment_intent: PaymentIntentWrapper,
callback: Box<dyn FnOnce(PaymentIntentWrapper, ProcessPaymentError)>,
);
}
// ...
}
In general, when bridging Rust types to Swift we try to preserve as many of Rusty invariants as we can.
For example, if you pass a &SomeRustType to Swift it is not possible for Swift to call any of SomeRustType's mutable methods. This is enforced at compile time by taking advantage of Swift's class inheritence https://chinedufn.github.io/swift-bridge/bridge-module/opaque-types/index.html#owned-ref-and-refmut.
In the case of bridging FnOnce functions we wanted to guarantee that Swift could only call the function 0 or 1 times.
This is currently enforced at runtime by having Swift raise a fatal error if you call the callback more than once.
Let me know if you know of a good way to enforce this at compile time.
If you're curious about how the generated code looks you can see examples here https://github.com/chinedufn/swift-bridge/blob/2ad1757cc3858f03adf52aef7c8734157900bb3c/crates/swift-bridge-ir/src/codegen/codegen_tests/boxed_fnonce_tests.rs#L22
If you're interested in running Rust on iOS or other Apple hardware there are plenty of ways that you could contribute to swift-bridge's code generation and ergonomics.
I'd be more than happy to create some good first issues for you to hack on!
Cheers!