fmeow / maybe-async-rs Goto Github PK
View Code? Open in Web Editor NEWA procedure macro to unify SYNC and ASYNC implementation for downstream application/crates
Home Page: https://docs.rs/maybe-async
License: MIT License
A procedure macro to unify SYNC and ASYNC implementation for downstream application/crates
Home Page: https://docs.rs/maybe-async
License: MIT License
The reason I am asking might very well be ignorance, but here I go.
When transforming the git-transport
crate to also support async, I am starting out with blocking code. Right now I use feature toggles as follows:
is_async
blocking-client
makes the existing blocking client code availableThe problem I am having with this is my inability to conditionally not compile the async-trait
crate, as it's required for the async code, but not for the blocking code. However, cargo features are additive and can't be turned off, leaving the blocking code with the need to compile async-trait
.
This issue is certainly minor, but if valid might point at an opportunity.
With a feature flag like maybe-async/is_async
, I could have the following configuration.
async-client
, we also require maybe-async/is_async
and async-trait
blocking-client
, we require maybe-async/is_sync
--all-features
, we pick one of the above to be the dominant oneWhile only having taken a glance at how the is_sync
feature toggle is used in this crate, I have a feeling that the addition of is_async
would be very possible. If so, I would be happy to contribute it, too.
I am curious to hear what you are thinking. Thanks a lot.
Hello! First of all, thanks a lot for creating this crate! It's super useful, we're using it at https://github.com/ramsayleung/rspotify.
The only problem we've found is that when maybe-async/is_sync
is activated, these warnings appear:
warning: unnecessary parentheses around block return value
--> src/lib.rs:11:12
|
11 | base().await
| ^^^^^ help: remove these parentheses
|
= note: `#[warn(unused_parens)]` on by default
Here's a minimal snippet:
//! lib.rs
use maybe_async::{sync_impl, maybe_async};
#[sync_impl]
fn base() -> ureq::Response {
ureq::get("https://myapi.example.com/ingest")
.call()
}
#[maybe_async]
pub async fn current_user() -> ureq::Response {
base().await
}
# Cargo.toml
[package]
name = "notworking"
version = "0.1.0"
authors = ["Mario Ortiz Manero <[email protected]>"]
edition = "2018"
[dependencies]
async-trait = "0.1.40"
maybe-async = "0.2.0"
ureq = "1.4.1"
[features]
default = ["maybe-async/is_sync"]
Any idea of what this could be? Thanks in advance.
As written in the title, I don't think there is currently a solution to only spit out attributes with maybe_async
. Am I overlooking things? I want the following to compile:
#[tokio::main]
#[maybe_async::maybe_async]
async fn main() {
let user = User::new("kasuporo");
// Now you are able to:
// Get overview
let overview = user.overview(None).await;
// Get submitted posts.
let submitted = user.submitted(None).await;
// Get comments.
let comments = user.comments(None).await;
}
The maybe_async
attribute works fine when is_sync
is enabled but the resolved code is the following:
#[tokio::main]
fn main() {
let user = User::new("kasuporo");
// Now you are able to:
// Get overview
let overview = user.overview(None);
// Get submitted posts.
let submitted = user.submitted(None);
// Get comments.
let comments = user.comments(None);
}
Sadly, tokio::main
remains. Is there something I am missing?
I have the following test case:
#[cfg(test)]
mod tests {
#[cfg(feature = "async")]
use tokio;
#[maybe_async::test(
feature="blocking",
async(feature = "async", tokio::test)
)]
async fn test_oauth() {
...
let client = dbg!Reddit::new(&USER_AGENT, &client_id, &client_secret)
.username(&username)
.password(&password)
.login()
.await;
}
}
The compiler throws the following error:
error[E0728]: `await` is only allowed inside `async` functions and blocks
--> tests\tests.rs:34:21
|
23 | async fn test_oauth() {
| ---------- this is not `async`
...
34 | .login()
| _____________________^
35 | | .await);
| |__________________^ only allowed inside `async` functions and blocks
error[E0728]: `await` is only allowed inside `async` functions and blocks
--> tests\tests.rs:41:28
|
23 | async fn test_oauth() {
| ---------- this is not `async`
...
41 | assert!(me.me().await.is_ok());
| ^^^^^^ only allowed inside `async` functions and blocks
error[E0277]: `Result<Me, RouxError>` is not a future
--> tests\tests.rs:34:21
|
34 | .login()
| _____________________^
35 | | .await);
| |__________________^ `Result<Me, RouxError>` is not a future
|
= help: the trait `Future` is not implemented for `Result<Me, RouxError>`
= note: Result<Me, RouxError> must be a future or must implement `IntoFuture` to be awaited
= note: required because of the requirements on the impl of `IntoFuture` for `Result<Me, RouxError>`
help: remove the `.await`
|
34 - .login()
34 + .login());
|
According to the readme I can apply the macro to struct definitions. However if I do this on an impl
block for a struct (without trait) I get an error due to this check:
Line 26 in b8be6e4
Line 43 in b8be6e4
impl
in order to affect all its methods. Is there something wrong with my line of thinking? If not I could try to come up with an PR for this.This crate currently uses the async_trait
crate to support async functions in traits.
My understanding of async is not very deep but I wonder if it would be possible to use the implementation that is available on nightly instead? (see https://blog.rust-lang.org/inside-rust/2022/11/17/async-fn-in-trait-nightly.html)
Hey!
I just noticed something that seems kind of bad! The is_sync
feature of maybe-async breaks stuff. Here's an example:
Create a new, empty crate, add these dependencies:
crates-index = { version = "2.1.1", features = ["git", "git-https"] }
rust-s3 = "0.33.0"
Building this will break hard. Disabling either one of the dependencies makes the build work.
Why? Both of these crates us maybe-async
. The crates-index
crate enables the is_sync
feature. The presence of this feature breaks rust-s3
.
My understanding is that this feature is not "additive".
Can someone look into this? In my project, I am able to work around this, but this issue currently makes doing cargo build
on the workspace level broken. In my workspace I can work around it by always compiling crates individually. But maybe it would be a good idea to peek into this and see if there was some way to solve this.
Cheers!
Hello,
I use the maybe_async
crate for a few projects, and it works great. I only have a issue on the tests:
This is a test from my crate aragog based on your arangors
crate.
#[maybe_async::test(
feature = "blocking",
async(all(not(feature = "blocking")), tokio::test)
)]
async fn can_be_recorded_and_retrieved() -> Result<(), String> {
with_db(|pool| async move {
let dish = init_dish();
let dish_record = DatabaseRecord::create(dish, &pool).await.unwrap();
let found_record = Dish::find(&dish_record.key, &pool).await.unwrap();
common::expect_assert_eq(dish_record.record, found_record.record)?;
Ok(())
})
.await
}
When async
it works fine, but when I use the blocking
feature the async move
is not changed making the test build fail.
I'm wondering if handling this case could be done through mabe_async
Otherwise I can chang the way I write my db-related tests.
EDIT:
the with_db
function is the following:
#[maybe_async::maybe_async]
pub async fn with_db<T, F>(test: T) -> Result<(), String>
where
T: FnOnce(DatabaseConnectionPool) -> F,
F: Future<Output = Result<(), String>>,
{
let db_pool = setup_db().await;
test(db_pool).await
}
Mybe the issue is comming for the F
bound to a Future
Hi,
I am trying to compile a project that depends on two crates that use maybe-async
internally. One crate sets the is_sync
flag while the other doesn't. The problem I am having is that the is_sync
flag set by one crate is applied also to the other crate that requires async and the project fails to compile. All projects use resolver v2.
I believe this problem was discussed in this Reddit post.
I have created a minimal example to show this problem:
main.rs
use maybe_async::{async_impl, sync_impl};
#[cfg(not(feature = "is_sync"))]
use async_std::{io, task::{Context, Poll}};
#[cfg(feature = "is_sync")]
use std::io;
struct ReadInner<R> {
inner: R,
}
impl<R: io::Read> io::Read for ReadInner<R> {
#[sync_impl]
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.inner.read(buf)
}
#[async_impl]
fn poll_read(self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut [u8]) -> Poll<Result<usize>> {
self.inner.poll_read(cx, buf)
}
}
fn main() {
println!("Hello, world!");
}
Cargo.toml
[dependencies]
async-std = "1.9"
maybe-async = {version = "0.2", features = ["is_sync"]}
[features]
is_sync = []
$ cargo check --features is_sync
Checking asyncer v0.1.0 (/home/geoff/temp/asyncer)
Finished dev [unoptimized + debuginfo] target(s) in 0.02s
$ cargo check
Checking asyncer v0.1.0 (/home/geoff/temp/asyncer)
error[E0407]: method `read` is not a member of trait `io::Read`
--> src/main.rs:14:5
|
14 | / fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
15 | | self.inner.read(buf)
16 | | }
| |_____^ not a member of trait `io::Read`
warning: unused imports: `Context`, `Poll`
--> src/main.rs:4:28
|
4 | use async_std::{io, task::{Context, Poll}};
| ^^^^^^^ ^^^^
|
= note: `#[warn(unused_imports)]` on by default
error[E0046]: not all trait items implemented, missing: `poll_read`
--> src/main.rs:12:1
|
12 | impl<R: io::Read> io::Read for ReadInner<R> {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ missing `poll_read` in implementation
|
= help: implement the missing item: `fn poll_read(self: Pin<&mut Self>, _: &mut Context<'_>, _: &mut [u8]) -> Poll<Result<usize, std::io::Error>> { todo!() }`
error[E0599]: no method named `read` found for type parameter `R` in the current scope
--> src/main.rs:15:20
|
15 | self.inner.read(buf)
| ^^^^ method not found in `R`
|
= help: items from traits can only be used if the trait is in scope
help: the following traits are implemented but not in scope; perhaps add a `use` for one of them:
|
1 | use std::io::Read;
|
1 | use async_std::io::ReadExt;
|
1 | use futures_lite::io::AsyncReadExt;
|
error: aborting due to 3 previous errors; 1 warning emitted
Some errors have detailed explanations: E0046, E0407, E0599.
For more information about an error, try `rustc --explain E0046`.
error: could not compile `asyncer`
To learn more, run the command again with --verbose.
I maintain some libraries that have sync and async users. The async users wrap calls in spawn_blocking
, which works, but isn't ergonomic and may be suboptimal. maybe_async
looks like it could potentially be (part of) an alternative approach.
If I read the docs right, maybe_async
can support both sync and async users, but by using a cargo feature, it can't support both in the same program. If the library gets used in multiple places in the same program, and one of them enables is_sync
, it would break the other places that need async.
It seems like it would be useful to have a macro that expands to both sync and async, using different names, so that they can coexist. For example,
#[maybe_async::both]
async fn foo() -> bool {
true
}
might expand to:
#[cfg(feature = "async")]
async fn async_foo() -> bool {
true
}
#[cfg(feature = "sync")]
fn sync_foo() -> bool {
true
}
so that users can call whichever form they need by name (though I'm not attached to the particular naming convention here).
Change unit test as following:
diff --git a/tests/ui/03-must-be-sync.rs b/tests/ui/03-must-be-sync.rs
index 0bccfcc..69be40c 100644
--- a/tests/ui/03-must-be-sync.rs
+++ b/tests/ui/03-must-be-sync.rs
@@ -22,6 +22,17 @@ pub trait PubTrait {
}
}
+#[maybe_async::maybe_async]
+pub(crate) trait CratePubTrait {
+ fn sync_fn() {}
+
+ async fn declare_async(&self);
+
+ async fn async_fn(&self) {
+ self.declare_async().await
+ }
+}
+
#[maybe_async::maybe_async]
async fn async_fn() {}
@@ -52,7 +63,6 @@ fn main() -> std::result::Result<(), ()> {
Ok(())
}
-
#[cfg(not(feature = "is_sync"))]
#[async_std::main]
async fn main() {}
then simply run cargo test
, failed as following:
test tests/ui/03-must-be-sync.rs [should pass] ... error
┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈
error: expected `fn`
--> tests/ui/03-must-be-sync.rs:26:12
|
26 | pub(crate) trait CratePubTrait {
| ^^^^^
┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.