# implicit-await - implicit `Future` awaiting for Rust ## Usage A **nightly** Rust compiler is required until the [async_await](https://github.com/rust-lang/rust/issues/50547) feature is stabilized. Add this to your `Cargo.toml`: ```toml [dependencies] implicit-await = "0.1" ``` Now you can use `#[implicit_await]`: ```rust use implicit_await::implicit_await; #[implicit_await] async fn foo() { // Any functions returning a future (`async fn` or `fn -> impl Future`) will be automatically awaited. } ``` ## Description Rust's async/await feature uses the postfix `.await` operator to explicitly request suspension points within `async` functions. The discussion around the syntax of the `.await` operator has been long and contentious, driven by the desire to interoperate smoothly with Rust's existing postfix `?` operator (among other concerns). When I was reading about the various syntax proposals from which `.await` finally emerged the leader, it occurred to me that the problems would be greatly simplified by making the wrapping/unwrapping behavior of `async` functions internally consistent. In standard Rust, `async` functions implicitly wrap their return values in a `Future`, but the *inputs* to `async` functions - the child `Futures` that determine the shape of the compiler-generated state machine - are explicitly unwrapped via `.await`. If instead `async` functions handled both wrapping and unwrapping implicitly, then there would be no need for the `.await` operator, bypassing the syntax concerns about the operator. This library provides a procedural macro `#[implicit_await]` which transforms `async` functions to implicitly unwrap child `Future`s. | | Standard Rust | #[implicit_await] | |-----------------------------------|-------------------------------|----------------------| | Outputs (function results) are... | implicitly wrapped | implicitly wrapped | | Inputs (child futures) are... | explicitly unwrapped (.await) | implicitly unwrapped | ## Example See the [sum](implicit-await/examples/sum.rs) example for a full working demo of the following. Suppose you have the following three functions, which range from fully synchronous to fully asynchronous. ```rust type IntResult = Result; // Fully synchronous fn num_sync(num: u32) -> IntResult { Ok(num) } // Half-and-half fn num_fut(num: u32) -> impl Future { ready(Ok(num)) } // Fully asynchronous async fn num_async(num: u32) -> IntResult { Ok(num) } ``` In standard Rust, calls to synchronous and asynchronous functions are distinguished by the need to postfix the `.await` operator. ```rust async fn sum() -> IntResult { // No .await - a synchronous function. let one: u32 = num_sync(1)?; // .await - asynchronous function. let two: u32 = num_fut(2).await?; // .await - asynchronous function. let three: u32 = num_async(3).await?; // Note that the return value is implicitly wrapped. Ok(one + two + three) } ``` With `implicit-await`, synchronous and asynchronous functions behave equivalently due to the implicit awaiting behavior. This allows the `?` operator to work as expected without any additional syntax. ```rust #[implicit_await] async fn sum() -> IntResult { // Synchronous function. let one: u32 = num_sync(1)?; // A `fn -> impl Future` is implicitly awaited. let two: u32 = num_fut(2)?; // An `async fn` is implicitly awaited. let three: u32 = num_async(3)?; Ok(one + two + three) } ``` `implicit-await` makes calls to asynchronous functions behave identically to calls to synchronous functions from the point of view of program control flow. The intuitive rule that *functions complete before they return*, which Rust programmers naturally learn from calling synchronous functions, also applies to asynchronous function calls. However, sometimes you have use-cases where you want explicit control over suspension points. The `defer!` macro allows you to *defer* awaits so that you can start multiple asynchronous processes in parallel. A common example of this use case is making multiple network requests at the same time, then waiting for all of them to complete. ```rust use implicit_await::defer; #[implicit_await] async fn sum() -> IntResult { // Start three futures in parallel without implicitly awaiting. let (one, two, three) = defer!{ ( num_fut(1), num_async(2), num_async(3) ) }; // This `join` call is implicitly awaited because the defer! block ended. let (one, two) = futures::future::join(one, two); // If you have a bare `Future`, you're free to .await it if you want. let three = three.await?; Ok(one? + two? + three); } ``` ## What's the catch? In a nutshell: ``error[E0599]: no method named `as_future` found for type `your::type::here` in the current scope`` The catch is that Rust doesn't support negative or mutually exclusive trait bounds. Because of this, I needed to make a choice whether to support `Future` types seamlessly and `!Future` types painfully, or `!Future` types seamlessly and `Future` types painfully. I chose the former. This means that types which do not implement `Future` need to be manually supported. | You're calling sync functions that return... | Then you need to... | |----------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Types from `std` | Do nothing ("std" is a default feature) | | Types from a third-party crate | Check the features list to see whether implicit-await already supports that crate, and if so enable the feature. Otherwise submit an issue requesting support for that crate's types be added. Rockstars can include a PR to add support along with the issue! | | Types from your own crate | Use the as_future! macro to add support in your own code. OR submit a PR to implicit-await to support your crate behind a feature flag. ## Features * `std` (*default-feature*) - Enable support for Rust's standard library types. Disable this feature for `no-std` support. ## TODOs * Fix bug in the as_future! macro which makes multi-generic invocations not work. * Go through all of `std` adding types * Come up with a list of other crates to add support for, and do so. ## Dependencies I strive to limit dependencies where possible. The direct dependencies of the `implicit-await` project are `proc-macro2`, `quote`, and `syn`, all used to implement the `#[implicit_await]` procedural macro. `unicode-xid` is brought in as a transitive dependency. ``` implicit-await v0.1.0 └── implicit-await-macro v0.1.0 ├── proc-macro2 v0.4.30 │ └── unicode-xid v0.1.0 ├── quote v0.6.12 │ └── proc-macro2 v0.4.30 (*) └── syn v0.15.34 ├── proc-macro2 v0.4.30 (*) ├── quote v0.6.12 (*) └── unicode-xid v0.1.0 (*) ``` ## Acknowledgements Apparently futures in Kotlin work similarly to this.