{"id":13672439,"url":"https://github.com/fMeow/maybe-async-rs","last_synced_at":"2025-04-27T22:32:15.129Z","repository":{"id":39922959,"uuid":"233786645","full_name":"fMeow/maybe-async-rs","owner":"fMeow","description":"A procedure macro to unify SYNC and ASYNC implementation for downstream application/crates","archived":false,"fork":false,"pushed_at":"2024-02-22T03:04:22.000Z","size":130,"stargazers_count":151,"open_issues_count":6,"forks_count":18,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-03-28T20:17:22.618Z","etag":null,"topics":["async","proc-macro","rust"],"latest_commit_sha":null,"homepage":"https://docs.rs/maybe-async","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/fMeow.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-01-14T07:52:11.000Z","updated_at":"2025-03-21T21:51:09.000Z","dependencies_parsed_at":"2024-06-19T00:05:23.956Z","dependency_job_id":"e2ffbc6a-1cd4-453f-afec-98a716c64ef2","html_url":"https://github.com/fMeow/maybe-async-rs","commit_stats":{"total_commits":89,"total_committers":7,"mean_commits":"12.714285714285714","dds":0.0674157303370787,"last_synced_commit":"b32a81704f6d84576d77e06882a06570e7f38de9"},"previous_names":[],"tags_count":15,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fMeow%2Fmaybe-async-rs","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fMeow%2Fmaybe-async-rs/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fMeow%2Fmaybe-async-rs/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fMeow%2Fmaybe-async-rs/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/fMeow","download_url":"https://codeload.github.com/fMeow/maybe-async-rs/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":251219601,"owners_count":21554444,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["async","proc-macro","rust"],"created_at":"2024-08-02T09:01:35.682Z","updated_at":"2025-04-27T22:32:14.727Z","avatar_url":"https://github.com/fMeow.png","language":"Rust","readme":"# maybe-async\n\n**Why bother writing similar code twice for blocking and async code?**\n\n[![Build Status](https://github.com/fMeow/maybe-async-rs/workflows/CI%20%28Linux%29/badge.svg?branch=main)](https://github.com/fMeow/maybe-async-rs/actions)\n[![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE)\n[![Latest Version](https://img.shields.io/crates/v/maybe-async.svg)](https://crates.io/crates/maybe-async)\n[![maybe-async](https://docs.rs/maybe-async/badge.svg)](https://docs.rs/maybe-async)\n\nWhen implementing both sync and async versions of API in a crate, most API\nof the two version are almost the same except for some async/await keyword.\n\n`maybe-async` help unifying async and sync implementation by **procedural\nmacro**.\n- Write async code with normal `async`, `await`, and let `maybe_async`\n  handles\nthose `async` and `await` when you need a blocking code.\n- Switch between sync and async by toggling `is_sync` feature gate in\n  `Cargo.toml`.\n- use `must_be_async` and `must_be_sync` to keep code in specified version\n- use `async_impl` and `sync_impl` to only compile code block on specified\n  version\n- A handy macro to unify unit test code is also provided.\n\nThese procedural macros can be applied to the following codes:\n- trait item declaration\n- trait implementation\n- function definition\n- struct definition\n\n**RECOMMENDATION**: Enable **resolver ver2** in your crate, which is\nintroduced in Rust 1.51. If not, two crates in dependency with conflict\nversion (one async and another blocking) can fail compilation.\n\n\n### Motivation\n\nThe async/await language feature alters the async world of rust.\nComparing with the map/and_then style, now the async code really resembles\nsync version code.\n\nIn many crates, the async and sync version of crates shares the same API,\nbut the minor difference that all async code must be awaited prevent the\nunification of async and sync code. In other words, we are forced to write\nan async and a sync implementation respectively.\n\n### Macros in Detail\n\n`maybe-async` offers 4 set of attribute macros: `maybe_async`,\n`sync_impl`/`async_impl`, `must_be_sync`/`must_be_async`,  and `test`.\n\nTo use `maybe-async`, we must know which block of codes is only used on\nblocking implementation, and which on async. These two implementation should\nshare the same function signatures except for async/await keywords, and use\n`sync_impl` and `async_impl` to mark these implementation.\n\nUse `maybe_async` macro on codes that share the same API on both async and\nblocking code except for async/await keywords. And use feature gate\n`is_sync` in `Cargo.toml` to toggle between async and blocking code.\n\n- `maybe_async`\n\n    Offers a unified feature gate to provide sync and async conversion on\n    demand by feature gate `is_sync`, with **async first** policy.\n\n    Want to keep async code? add `maybe_async` in dependencies with default\n    features, which means `maybe_async` is the same as `must_be_async`:\n\n    ```toml\n    [dependencies]\n    maybe_async = \"0.2\"\n    ```\n\n    Want to convert async code to sync? Add `maybe_async` to dependencies with\n    an `is_sync` feature gate. In this way, `maybe_async` is the same as\n    `must_be_sync`:\n\n    ```toml\n    [dependencies]\n    maybe_async = { version = \"0.2\", features = [\"is_sync\"] }\n    ```\n\n    There are three usage variants for `maybe_async` attribute usage:\n    - `#[maybe_async]` or `#[maybe_async(Send)]`\n\n       In this mode, `#[async_trait::async_trait]` is added to trait declarations and trait implementations\n       to support async fn in traits.\n\n    - `#[maybe_async(?Send)]`\n\n       Not all async traits need futures that are `dyn Future + Send`.\n       In this mode, `#[async_trait::async_trait(?Send)]` is added to trait declarations and trait implementations,\n       to avoid having \"Send\" and \"Sync\" bounds placed on the async trait\n       methods.\n\n    - `#[maybe_async(AFIT)]`\n\n       AFIT is acronym for **a**sync **f**unction **i**n **t**rait, stabilized from rust 1.74\n\n    For compatibility reasons, the `async fn` in traits is supported via a verbose `AFIT` flag. This will become\n    the default mode for the next major release.\n\n- `must_be_async`\n\n    **Keep async**.\n\n    There are three usage variants for `must_be_async` attribute usage:\n    - `#[must_be_async]` or `#[must_be_async(Send)]`\n    - `#[must_be_async(?Send)]`\n    - `#[must_be_async(AFIT)]`\n\n- `must_be_sync`\n\n    **Convert to sync code**. Convert the async code into sync code by\n    removing all `async move`, `async` and `await` keyword\n\n\n- `sync_impl`\n\n    A sync implementation should compile on blocking implementation and\n    must simply disappear when we want async version.\n\n    Although most of the API are almost the same, there definitely come to a\n    point when the async and sync version should differ greatly. For\n    example, a MongoDB client may use the same API for async and sync\n    version, but the code to actually send reqeust are quite different.\n\n    Here, we can use `sync_impl` to mark a synchronous implementation, and a\n    sync implementation should disappear when we want async version.\n\n- `async_impl`\n\n    An async implementation should on compile on async implementation and\n    must simply disappear when we want sync version.\n\n    There are three usage variants for `async_impl` attribute usage:\n    - `#[async_impl]` or `#[async_impl(Send)]`\n    - `#[async_impl(?Send)]`\n    - `#[async_impl(AFIT)]`\n\n- `test`\n\n    Handy macro to unify async and sync **unit and e2e test** code.\n\n    You can specify the condition to compile to sync test code\n    and also the conditions to compile to async test code with given test\n    macro, e.x. `tokio::test`, `async_std::test`, etc. When only sync\n    condition is specified,the test code only compiles when sync condition\n    is met.\n\n    ```rust\n    # #[maybe_async::maybe_async]\n    # async fn async_fn() -\u003e bool {\n    #    true\n    # }\n\n    ##[maybe_async::test(\n        feature=\"is_sync\",\n        async(\n            all(not(feature=\"is_sync\"), feature=\"async_std\"),\n            async_std::test\n        ),\n        async(\n            all(not(feature=\"is_sync\"), feature=\"tokio\"),\n            tokio::test\n        )\n    )]\n    async fn test_async_fn() {\n        let res = async_fn().await;\n        assert_eq!(res, true);\n    }\n    ```\n\n### What's Under the Hook\n\n`maybe-async` compiles your code in different way with the `is_sync` feature\ngate. It removes all `await` and `async` keywords in your code under\n`maybe_async` macro and conditionally compiles codes under `async_impl` and\n`sync_impl`.\n\nHere is a detailed example on what's going on whe the `is_sync` feature\ngate set or not.\n\n```rust\n#[maybe_async::maybe_async(AFIT)]\ntrait A {\n    async fn async_fn_name() -\u003e Result\u003c(), ()\u003e {\n        Ok(())\n    }\n    fn sync_fn_name() -\u003e Result\u003c(), ()\u003e {\n        Ok(())\n    }\n}\n\nstruct Foo;\n\n#[maybe_async::maybe_async(AFIT)]\nimpl A for Foo {\n    async fn async_fn_name() -\u003e Result\u003c(), ()\u003e {\n        Ok(())\n    }\n    fn sync_fn_name() -\u003e Result\u003c(), ()\u003e {\n        Ok(())\n    }\n}\n\n#[maybe_async::maybe_async]\nasync fn maybe_async_fn() -\u003e Result\u003c(), ()\u003e {\n    let a = Foo::async_fn_name().await?;\n\n    let b = Foo::sync_fn_name()?;\n    Ok(())\n}\n```\n\nWhen `maybe-async` feature gate `is_sync` is **NOT** set, the generated code\nis async code:\n\n```rust\n// Compiled code when `is_sync` is toggled off.\ntrait A {\n    async fn maybe_async_fn_name() -\u003e Result\u003c(), ()\u003e {\n        Ok(())\n    }\n    fn sync_fn_name() -\u003e Result\u003c(), ()\u003e {\n        Ok(())\n    }\n}\n\nstruct Foo;\n\nimpl A for Foo {\n    async fn maybe_async_fn_name() -\u003e Result\u003c(), ()\u003e {\n        Ok(())\n    }\n    fn sync_fn_name() -\u003e Result\u003c(), ()\u003e {\n        Ok(())\n    }\n}\n\nasync fn maybe_async_fn() -\u003e Result\u003c(), ()\u003e {\n    let a = Foo::maybe_async_fn_name().await?;\n    let b = Foo::sync_fn_name()?;\n    Ok(())\n}\n```\n\nWhen `maybe-async` feature gate `is_sync` is set, all async keyword is\nignored and yields a sync version code:\n\n```rust\n// Compiled code when `is_sync` is toggled on.\ntrait A {\n    fn maybe_async_fn_name() -\u003e Result\u003c(), ()\u003e {\n        Ok(())\n    }\n    fn sync_fn_name() -\u003e Result\u003c(), ()\u003e {\n        Ok(())\n    }\n}\n\nstruct Foo;\n\nimpl A for Foo {\n    fn maybe_async_fn_name() -\u003e Result\u003c(), ()\u003e {\n        Ok(())\n    }\n    fn sync_fn_name() -\u003e Result\u003c(), ()\u003e {\n        Ok(())\n    }\n}\n\nfn maybe_async_fn() -\u003e Result\u003c(), ()\u003e {\n    let a = Foo::maybe_async_fn_name()?;\n    let b = Foo::sync_fn_name()?;\n    Ok(())\n}\n```\n\n### Examples\n\n#### rust client for services\n\nWhen implementing rust client for any services, like awz3. The higher level\nAPI of async and sync version is almost the same, such as creating or\ndeleting a bucket, retrieving an object, etc.\n\nThe example `service_client` is a proof of concept that `maybe_async` can\nactually free us from writing almost the same code for sync and async. We\ncan toggle between a sync AWZ3 client and async one by `is_sync` feature\ngate when we add `maybe-async` to dependency.\n\n\n## License\nMIT\n","funding_links":[],"categories":["Rust"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FfMeow%2Fmaybe-async-rs","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FfMeow%2Fmaybe-async-rs","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FfMeow%2Fmaybe-async-rs/lists"}