-
Notifications
You must be signed in to change notification settings - Fork 211
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
feat: Add support for async/Future #1409
Conversation
I'm not sure about this type yet. It's still highly experimental and unstable.
…op values as expected.
This reverts commit 2ab5c10.
@bendk The last blocker is the update of the Docker container image (as explained here). Can we unblock this situation? Is it possible for someone else to update this image? |
I wish I could, but I don't have access to push the container. It would be really nice if the CI used the Dockefile from the repo. I don't have time today, but I'll check on this tomorrow. |
Is this still the "That only works if you're rfkelly; we need to figure out a better strategy for maintainership of said docker image." problem mentioned in https://github.com/mozilla/uniffi-rs/blob/main/docker/README.md? Let me know if there's something I can do to help unblock things here in the short term 😬 |
@rfk Thanks, but I think we can have a different short-term fix. @Hywan I think the best path forward is to make these tests not run in CI. The first step there is making the other tests still pass. I made a quick commit that makes it so we only import the async libraries if there are actually async functions. I think that should make them pass again. Hopefully, just a few more steps after that:
|
Do you mean to add checksums for each JAR or stuff like that we download inside the |
So, the Docker image cannot run the Swift and Kotlin tests because:
This PR updates the This PR also disables the tests for Swift and Kotlin for the moment, waiting for the Docker image to be updated. |
CI is green \o/ |
The |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was away for most of the week, but this is looking great. Let's merge.
I think the only remaining issue is to merge main in and resolve the conflicts. |
@bendk Ready to be merged! |
Merged! Thanks for all the work here, I'm excited to continue to push this. Would you want a new release to use this functionality? |
We are using |
Hi, thanks for implementing this! However, I just tried it out on Suppose we have the following rust functions and we generate python bindings from them. #[uniffi::export]
pub fn add(left: u32, right: u32) -> u32 {
left + right
}
#[uniffi::export]
pub async fn async_add(left: u32, right: u32) -> u32 {
left + right
} As expected, the >>> mylib.add(1, 2)
3
>>> mylib.add(1, 2) + mylib.add(3, 4)
10 But when calling the >>> async def f():
... return await mylib.async_add(1, 2)
>>> async def g():
... val1 = await mylib.async_add(1, 2)
... val2 = await mylib.async_add(3, 4)
... return val1 + val2
>>> asyncio.run(f())
c_uint(3)
>>> asyncio.run(g())
TypeError: unsupported operand type(s) for +: 'c_uint' and 'c_uint' I would expect the type returned after awaiting the async function to be the same as the type of the non-async variety, but it's not. Potential bug? |
Not a bug. An oversight ;-). Please open a proper issue, and ping me. I'll fix it. |
I think there is a typo in the Swift example: async let alice = sayAfter(secs: 2, who: "Alice")
async let bob = sayAfter(secs: 2, who: "Bob") should be: async let alice = sayAfter(secs: 2, who: "Alice")
async let bob = sayAfter(secs: 3, who: "Bob") 3 sec for bob, not 2, to match the description, right? |
Please don't post comments on old PRs, open an issue instead. |
Quick explanation
This is a patch to add support for async/
Future
insideuniffi-rs
. At the time of writing, it works very well with Python, Swift and Kotlin.Any
Future<Output = T>
orFuture<Output = Result<T, E>>
, whereT: FfiReturn
, is supported. The code is documented and I reckon it's enough to understand the code. The main challenges are:Future<T>
with a C ABI,Future
into other foreign language executors/runtimes. The Rust model is very different, but since aFuture
does nothing by default and must be polled by an executor, that's actually perfect! In the case of Python, the executor isasyncio
, in the case of Swift, the executor is the platform itself, in the case of Kotlin, the executor is… whatsuspend
inherits. The biggest difficulty is to be able to provide a waker defined in the foreign language to Rust.The documentation with detailed explanations of this project lands here:
uniffi-rs/uniffi_core/src/ffi/rustfuture.rs
Lines 1 to 282 in 926135a
Overview
Let's start with the following Rust code:
Nothing special except that the async function is annotated with
#[uniffi::export]
.In Python
The function definition
How to use it
This code awaits on futures sequentially. It returns after$2 + 3 = 5$ seconds. However, since we generate proper Python
Future
-ish (we use our own type, but it's 100% compatible with the standardasyncio.Future
), we can createasyncio.Task
s to run them concurrently:This code runs in$\max(2, 3) = 3$ seconds.
In Swift
Now, let's jump on Swift! Contrary to Python where we implement our own
Future
-ish type, in Swift, the generated functions and methods are 100% native standard async functions.The function definition
How to use it
Hint: To run async code, we must be inside a
Task
.This code awaits on futures sequentially. It returns after$2 + 3 = 5$ seconds. But because we generate native async functions, it fits well with the native language concurrency constructions (like
async let
):This code runs in$\max(2, 3) = 3$ seconds.
In Kotlin
OK, Kotlin now! In the manner of Swift, the generated async functions, aka
suspend
ed functions use everything native from the Kotlin language.The function definition
How to use it
You can't really tell when a call to a function is sync or async in Kotlin, but trust me, they are async.
This code awaits on futures sequentially. It returns after$2 + 3 = 5$ seconds. Let's execute them concurrently with
async
:This code runs in$\max(2, 3) = 3$ seconds.
Deeper look
Generated code with annotations.
In Python
In Swift
In Kotlin
Progress
RustFuture<T>
T
)RustFuture.poll(self, waker) -> Poll<T>
uniffi_rustfuture_poll
assistant/helper function for C ABIuniffi_rustfuture_drop
assistant/helper function for C ABI#[uniffi::export]
supportsFuture
_poll
function_drop
functionRubynot planned, I don't need it, maybe laterFuture
#[uniffi::export(async_runtime = "tokio")]
to add the glue to make Tokio'sFuture
s compatible with the standardstd::future::Future
typeFuture<Output = ()>
Future<Output = Result<T, E>>
Future::poll