-
Notifications
You must be signed in to change notification settings - Fork 110
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
Remove async_trait
dependency
#932
Conversation
async_trait
dependencyasync_trait
dependency
f2aa06b
to
2a6e87e
Compare
v2: adjusted the docs so CI passes |
There is a trick similar to what
This approach is actually suggested in one of the blog posts about However:
I think we can keep the current approach with boxed futures. It's not very pretty but it works, and I think that users aren't going to implement our async traits too frequently so I think we can live with that as well. Personally, I don't think it's worth it to use async traits yet, perhaps there will be a better official solution to this problem in the future and we might switch to it in the next stable version after the solution appears. |
scylla/src/transport/session.rs
Outdated
fn translate_address<'a>( | ||
&'a self, | ||
untranslated_peer: &'a UntranslatedPeer, | ||
) -> BoxFuture<'_, Result<SocketAddr, TranslationError>>; |
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.
By using the BoxFuture
alias, now you are putting the futures
crate into our API.
Although futures
, despite it being a pre-1.0 crate, is actually pretty stable (0.3.0 was released 4 years ago), I think it's completely unnecessary and we could just use Pin<Box<dyn Future<Output = Result<SocketAddr, TranslationError>> + 'a>>
here (or something in that fashion).
2a6e87e
to
7f9cbf0
Compare
v2:
|
scylla/src/utils/futures.rs
Outdated
use std::future::Future; | ||
use std::pin::Pin; | ||
|
||
pub(crate) type BoxedFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>; |
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.
It's not a pub item but it appears in public traits. How does it interact with the documentation? I'd rather users didn't encounter an opaque BoxFuture
alias. Users should know what is the definition, that it requires the future to be Send
etc. - I think this information should be available if you make BoxedFuture
public.
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.
Yes, you are right. From what I've tested the users trying to implement the trait encounter an opaque type (BoxedFuture
) and cannot import it (since the definition is pub(crate)
).
We need to introduce an alias for boxed futures, so clippy does not complain about the complexity of return types.
Removed async_trait usage from AddressTranslator. Since this trait is used for the trait objects, we cannot use an async trait functionalities introduced in Rust 1.75. Such traits are not object-safe. We need to box the returned future.
Replaced usages of async_trait for AuthenticatorSession with boxed future. AuthenticatorSession trait is used for trait objects, so we cannot make use of plain async traits from introduced in Rust 1.75.
Unfortunately, starting from the following commit we will be boxing the futures returned by `AuthenticatorProvider`. This requires to wrap the returned type with `BoxFuture`, and so the resulting type becomes complex which results in clippy complaints. This is why we introduce a type alias for the pair representing an initial auth response and a boxed auth session.
Replaced usages of async_trait for AuthenticatorProvider with boxed futures.
Since the definition of trait functions have changed, we need to adjust the docstrings examples for SessionBuilder. Unfortunately, users implementing the traits will need to box the futures (and specify lifetimes).
The trait definitions using async_trait before have changed, and so the docs needed to be adjusted. The trait implementations in examples now return boxed futures as well.
7f9cbf0
to
e15686d
Compare
v3: made |
One question: will this require implementors to also drop |
/// Type to represent an authentication error message. | ||
pub type AuthError = String; | ||
|
||
/// Type to represent an initial auth response with an authenticator session. | ||
pub(crate) type AuthInitialResponseAndSession = (Option<Vec<u8>>, Box<dyn AuthenticatorSession>); |
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.
This is another case of the same problem of pub(crate)
in pub
item.
How does it look in generated documentation?
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.
Yeah, it's the same as for BoxedFuture
alias. There is no problem in the docs:
However, when I use the driver in some user application and generate the definition using vscode
hints, the generated function signature contains the opaque type:
struct Foo;
impl AuthenticatorProvider for Foo {
fn start_authentication_session<'a>(
&'a self,
authenticator_name: &'a str,
) -> scylla::BoxedFuture<
'_,
std::prelude::v1::Result<AuthInitialResponseAndSession, scylla::authentication::AuthError>,
> {
todo!()
}
}
What's interesting tho, is that the compiler can deduce the type in the error message when the function is not implemented:
not all trait items implemented, missing: `start_authentication_session`
--> src/main.rs:14:1
|
14 | impl AuthenticatorProvider for Foo {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ missing `start_authentication_session` in implementation
|
= help: implement the missing item: `fn start_authentication_session(&'a self, _: &'a str) -> Pin<Box<(dyn Future<Output = std::result::Result<(Option<Vec<u8>>, Box<(dyn AuthenticatorSession + 'static)>), String>> + Send + 'a)>> { todo!() }`
I'm not sure why vscode generates the function declaration with an opaque type.
I think that we should make this alias public as well. However, i think we should come up with a more fitting name for this type alias (or introduce a struct instead). I only introduced this alias because clippy was complaining about the complexity of the returned type and I wanted the alias to be used internally - unfortunately, it appears in the public trait declaration.
It won't work with current version - there is a problem with lifetime parameters/bounds. The async_trait implementation provides a bit different lifetime annotations/bounds than our trait function declarations. However, I just checked that we can play around a bit with the lifetimes in the declarations so the users can still implement it automatically using Let's look at the generated function signature using fn evaluate_challenge<'life0, 'life1, 'async_trait>(
&'life0 mut self,
_token: Option<&'life1 [u8]>
) -> ::core::pin::Pin<Box<dyn ::core::future::Future<Output = Result<Option<Vec<u8>>, AuthError>> + ::core::marker::Send + 'async_trait>>
where
'life0: 'async_trait,
'life1: 'async_trait,
Self: 'async_trait This is almost equivalent to our function declaration. Almost, because our current function declaration does not have a
But If we change the current function declaration from: fn evaluate_challenge<'a>(
&'a mut self,
token: Option<&'a [u8]>,
) -> BoxedFuture<'_, Result<Option<Vec<u8>>, AuthError>>; to the exact same declaration that fn evaluate_challenge<'a, 'b, 'c>(
&'a mut self,
_token: Option<&'b [u8]>,
) -> BoxedFuture<'c, Result<Option<Vec<u8>>, AuthError>>
where
'a: 'c,
'b: 'c,
Self: 'c; (and do the same for WDYT? Should we change the trait definitions as presented above? I think that then we could leave cc: @piodul |
Actually, now that I think about it, do we have to do all of this at all? Technically speaking, With the current direction, I think we are only doing disservice to ourselves by removing dependency on Unless my assumptions about @Lorak-mmk @muzarski what do you think? |
You are right, in case of proc macros they don't themselves have any public items. |
I think there is no problem for use to use the old version of |
I agree with the above comments - closing the PR since |
Ref: #771
Motivation
async-trait
is an unstable crate and so needs to be removed from public API to stabilize our API.Solution
Rust 1.75 async traits
In 1.75 release release of rust, it allows us to define traits with async functions. However, such traits are not object safe, and so they cannot be used in the trait objects (
dyn Trait
). Unfortunately, all of the traits usingasync_trait
are actually used in the trait objects. This means that we cannot simply remove theasync_trait
macro and expect everything to compile.Boxed futures
The other solution is to box the futures - the exact same thing that
async_trait
does for us.This is why, for each of the trait that used
async_trait
we replace the signatures of async functions from:to:
Notice that some trait functions, apart from
&self
reference, accept some additional reference. In result, we need to introduce a lifetime annotation to the signatures so the compiler can deduce the lifetime parameter of thefutures::BoxFuture
. Unfortunately, implementors of such traits will need to introduce these lifetime annotations as well. I'm not sure if there is a better solution to the issue.In addition, the implementors of the traits will need to explicitly box the returned futures like in the following example (or by using some
std::pin::Pin
utilities):Pre-review checklist
./docs/source/
.Fixes:
annotations to PR description.