-
Notifications
You must be signed in to change notification settings - Fork 125
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
router.to_async(), simple async/.await examples #281
Conversation
@alsuren - thanks for sharing your work on this. Cool to see examples as async/await solidifies. |
I've basically rewritten this from scratch. I would say that it is ready for review now. If you like this then |
Thanks for your efforts here @alsuren 👍 . |
I would vote to wait until #370 is done. |
@pksunkara I rebased on top of master and simplified things to match the simplification in simple_async_handlers (sleep() can no longer fail). We probably want another set of examples that shows error handling tricks, but I will leave that up to someone else. I have left the awkward |
Codecov Report
@@ Coverage Diff @@
## master #281 +/- ##
==========================================
+ Coverage 79.51% 79.59% +0.08%
==========================================
Files 105 106 +1
Lines 4964 5023 +59
==========================================
+ Hits 3947 3998 +51
- Misses 1017 1025 +8
Continue to review full report at Codecov.
|
extern crate serde; | ||
#[macro_use] | ||
extern crate serde_derive; | ||
extern crate tokio; |
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.
Please use 2018 edition. Don't need so many extern crate statements.
I just pushed a POC implementation of the router.to_async() function that I've been longing for. If you like it, please tell me what I need to do in order to get it merged. If you would rather merge this PR without it, please shout and I will revert it. |
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.
Minor changes needed
gotham/src/router/builder/single.rs
Outdated
Fut: Future<Output = HandlerResult> + Send + 'static, | ||
{ | ||
self.to_new_handler(move || Ok(move |s: State| handler(s).boxed())) | ||
} |
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.
I have implemented this directly on the trait because it feels like the most backwards-compatible thing to do.
In practice, the only implementation of DefineSingleRoute in the gotham codebase is SingleRouteBuilder. Would you rather I implement it on SingleRouteBuilder and force all external implementors of DefineSingleRoute to implement it?
Also, can someone look extra-hard at the marker traits that I've used on H
and Fut
, and make sure they're all appropriate? (I copied some of them from .to(), and I'm not sure they're all needed)
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.
I'm not sure. In a world where you don't care about backwards compatibility, which would you go for? The versioning model we are on allows us to break things, and we can provide documentation for migration if it's complicated.
The traits seem ok to me (when looking at a diff). I feel like Copy
and 'static
might be unnecessary...?
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.
Ideally, the .to
should map to (State, Response<Body>)
(normal fn), Pin<Box<HandlerFuture>>
(future fn) and HandlerResult
(async fn).
We can take it one step forward and disallow the mapping to (State, Response<Body>)
and replace it with HandlerResult
for consistency.
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.
If both future fn and async fn could not be supported at the same time and we are willing to break, then we should give priority to async fn and create a new to_future
or something for the future fn.
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.
@whitfin that sounds like the right question to ask. Good thinking. In an ideal world, I would probably think about making this trait private or something (I think that a recent compiler release that allows traits to be marked nonexhaustive, so they can't be implemented by external people?). In the short term, "program to the interface" seems to be the pattern that's being used here, so refactoring the implementation down into SingleRouteBuilder sounds like the thing to do. I will add this to the checklist, and do it once I have all of the other questions worked out.
@pksunkara: (I think that this is a separate discussion) as it stands in my PR (using your terminology) I can pass a "future fn" to .to_async()
, because Pin<Box> implements Future<Output = HandlerResult>
, but I can't pass "normal fn".
When I wrote this, I didn't think it's possible to create a single function that can accept both an impl Trait
and a struct. I seem to remember, rustc complaining that my blanket impl HandlerFuture
for impl Future
might conflict with my impl HandlerFuture
for (State, Response<Body>)
if someone else implements Future
for (State, Response<Body>)
... or something. You can get away with it for Pin<Box<HandlerFuture>>
because in that case your impl is over two struct types that the compiler can guarantee won't overlap. This was last year though, so I might be misremembering, or the compiler might have become more lenient since then.
I've been mulling it over in my head over the last couple of days, and I think that it might be possible to impl Future for (State, Response<Body>)
, and get on with our lives. Do you think that would work?
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.
error[E0117]: only traits defined in the current crate can be implemented for arbitrary types
--> src/lib.rs:7:1
|
7 | impl Future for (State, String) {
| ^^^^^^^^^^^^^^^^---------------
| | |
| | this is not defined in the current crate because tuples are always foreign
| impl doesn't use only types from inside the current crate
|
= note: define and implement a trait or new type instead
:(
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.
How about making the return object HandlerResult
for normal fn
?
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.
same deal: HandlerResult is just a Result that's generic over tuples, so it' also foreign.
9 | impl Future for HandlerResult {
| ^^^^^^^^^^^^^^^^-------------
| | |
| | `std::result::Result` is not defined in the current crate
| impl doesn't use only types from inside the current crate
I think that we need a separate .to()
for tuples vs .to_async()
for futures. It might be that we should call them .to_sync()
and .to()
though.
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.
The traits seem ok to me (when looking at a diff). I feel like Copy and 'static might be unnecessary...?
I had a quick play about and I couldn't see a way to get rid of them.
The handler callback is FnOnce + Copy
. This is a bit like Fn
, but a bit easier to implement with closures or something? If I remove the + Copy
then I end up with a handler function that can only be run once.
+ 'static
is required by .to_new_handler()
. It feels a bit excessive at first glance, but I think it's fine: the routes we define normally live for the entire duration of the program, and are normally implemented as top-level fn
s. I haven't really dug into whether there is a more appropriate lifetime than this, so I'm just going to keep what's there.
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.
I'm inclined to merge this unless there's objection before 5/21/20
@nyarly This is not really an objection but just some bikeshedding. Since 0.5.0 is a breaking change anyway, why not map |
@pksunkara I dislike the name change. At least I've used closures ( |
@@ -24,6 +24,9 @@ find yourself doing lots of CPU/memory intensive operations on the web server, | |||
then futures are probably not going to help your performance, and you might be | |||
better off spawning a new thread per request. | |||
|
|||
If you came here looking for an example that uses async/.await, please read | |||
[Async Request Handlers (.await version)](../simple_async_handlers). |
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.
I believe this link is incorrect as it points to itself if I'm not mistaken
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.
Good spot. Thanks.
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.
Merge on 5/21/20
In a previous iteration of this PR, I noticed in another project that I was wanting to use try!() a lot, now that my code looked like sync code, so I made a
handler_try!()
macro. In this version of the PR, I just use nested async blocks. Both are a bit ugly. I'm not sure which is better.It would be really nice if we could have an impl of Handler for async functions (it would let me collapse myThis pr now includes a .to_async() method on router.fn _() {Box::new(async {}.boxed(...).compat())}
boilerplate intoasync fn _() {...}
). Not the end of the world though.The examples are a bit contrived, because they don't call anything that can fail, so the lack of a working
?
operator isn't brought to the surface. It's probably worth porting at leastexamples/handlers/form_urlencoded
before we cut a release, and decide whether there is a way to passState
around that is compatible with?
(I probably won't have time for this until Febuary). This may cause us to change the signature of.to_async()
.