Skip to content
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

Async runtime for function binding defined in UDL file #2219

Open
lyzkov opened this issue Aug 16, 2024 · 10 comments
Open

Async runtime for function binding defined in UDL file #2219

lyzkov opened this issue Aug 16, 2024 · 10 comments

Comments

@lyzkov
Copy link

lyzkov commented Aug 16, 2024

I'm migrating from macros to UDL to decrease build time.

Is there any way to specify async runtime for an asynchronous function that has its definition in UDL marked with [Async] the same way I can do it with proc macro #[uniffi::export(async_runtime = "tokio")]?

[Error]
enum PingError {
  "IoError",
  "ParseError",
  "CustomError",
};

namespace ping_example {
  [Throws=PingError, Async]
  void start_listening();

  [Throws=PingError, Async]
  void dial_peer(string address);
};

From lib.rs:

/// Starts listening on all interfaces and a random, OS-assigned port.
pub async fn start_listening() -> Result<(), PingError> {
    // (...)
}

/// Dials the peer identified by the given multi-address.
pub async fn dial_peer(address: String) -> Result<(), PingError> {
    // (...)
}

Without async runtime specified I'm getting a runtime error:

Caught a panic calling rust code: "there is no reactor running, must be called from the context of a Tokio 1.x runtime"
@mhammond
Copy link
Member

I'm migrating from macros to UDL to decrease build time.

That's interesting!

Re your question, I think that's not possible, although I believe it would not be particularly difficult to add.

@lyzkov
Copy link
Author

lyzkov commented Aug 16, 2024

Re your question, I think that's not possible, although I believe it would not be particularly difficult to add.

I've started implementing the AsyncRuntime= attribute similar to Throws= but being new to Rust, I feel like a child lost in the darkness. I've succeeded in parsing a new UDL attribute, but I have no idea how to scaffold functions, methods, and constructors into future expressions of tokio. 🍡

If it comes to build time, it is not a big deal since UDL speeds up only clean build in my configuration. uniffi-bindgen in procmacro mode requires building dynamic library before generating bindings, and static library compilation for specific target must be repeated after then.
As such, I can compile and test cargo package in an external editor first while working on Rust code before integrating uncleaned build into Swift framework.
Nonetheless, it would be super nice to have fully integrated environment with incremental compilations for foreign targets. 😶‍🌫️🕊️

PS. What uniffi::export_for_udl macro is for? I can set async_runtime = "tokio" attribute for that macro, but I'm getting error[E0425]: cannot find function 'some_fun' in this scope whenever I apply it to any function, regardless of its signature's presence in the UDL file.

PS2. By so-called scaffolding I mean exporting pub fun, struct, enum as an c interface for foreign targets. As I understand: Semicompilation to C grants interoperability for FFI.

PS3. But what about UDL manifests for Xcompilation? Can UDLs be nested across cargo workspace for libraries that cross compile?

EDIT:
I've used async-std as an alternative for tokio. We will think about adding missing implementation for UDL later if that runtime is more benefitable as someone suggested on rust-libp2p.

@mhammond
Copy link
Member

Sorry for no response, I just got back from a break! I also misunderstood the problem when I said "not particularly difficult" :) This will require some fiddling in that macro code.

I don't understand the motivation.

uniffi-bindgen in procmacro mode requires building ...

uniffi-bindgen doesn't have a "procmacro mode". It does have a UDL mode, which generate .rs code - but that generated code uses the procmacros. I can't see how a UDL project could be faster to build than one without a UDL.

@lyzkov
Copy link
Author

lyzkov commented Aug 26, 2024

Forgive me for having little knowledge of crosscompilation. I'm guessing a lot without knowing, like a fool. ^^

In all the tutorials I've found, proc macros require me to compile the whole project to the default/native target before generating bindings for the foreign target. It is because of generate --library command that requires the dynamic binary file (dylib) to operate. Then, after generating bindings, I have to recompile the library from scratch because I need a static library for my foreign target.

With UDL, I can build my static library for foreign targets first and then generate bindings from .udl files without recompiling the library for the native target.

Is it possible to use a static library cross-compiled for the foreign target as a parameter for generate --library argument? This would fix my issue.

@mhammond
Copy link
Member

I see - although I guess I'm a little surprised you can't generate the bindings from the foreign target - maybe that's a better thing to lean in to? The UDL path will also bite you if you ever want to end up with multiple crates sharing uniffi types.

@lyzkov
Copy link
Author

lyzkov commented Aug 26, 2024

UDL path feels coarse, especially for the beginner, because there is hardly any support from the UniFFI parser in debugging mistakes in UDL syntax. But I have a lot of determination to go through.

My project is still in the proof-of-concept phase. The sooner it fails, the better decision I'll make for further development.
Till now, I've been building single crate packages from sample projects, but that can change soon. Are proc macros better suited for multi-crate interoperability?

@mhammond
Copy link
Member

Are proc macros better suited for multi-crate interoperability?

It's more like "multi-crate interoperability typically requires generating the bindings from a built library instead of from a single UDL". It should be possible to change this so you can supply all the UDL files, but I don't think that's implemented. The issue isn't proc-macros vs UDL, because UDL is largely implemented by leaning on proc-macros.

@mhammond
Copy link
Member

Back to the issue :) This is quite difficult due to only the .rs generation needing to know the runtime. It might be easier to have an option to allow all UDL async functions to specify the tokio runtime - that would be less intrusive (but still non-trivial - I think we'd want it specified in uniffi.toml, but the .rs code generation doesn't seem to read that) and might handle most use-cases.

@setoelkahfi
Copy link

setoelkahfi commented Aug 27, 2024

I have a similar issue. So, for now, does it mean this kind of function signature async fn api_call() -> Result<u64, ErrorResponse> can only be exported through the proc-macro syntax?

@lyzkov
Copy link
Author

lyzkov commented Aug 28, 2024

I have a similar issue. So, for now, does it mean this kind of function signature async fn api_call() -> Result<u64, ErrorResponse> can only be exported through the proc-macro syntax?

You can use async-std runtime with both UDL and proc macros but tokio runtime currently works only with proc macros.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants