[HN Gopher] Rust to stabilize `async fn` and return-position `im...
       ___________________________________________________________________
        
       Rust to stabilize `async fn` and return-position `impl Trait` in
       traits
        
       Author : saghm
       Score  : 203 points
       Date   : 2023-12-21 14:47 UTC (8 hours ago)
        
 (HTM) web link (blog.rust-lang.org)
 (TXT) w3m dump (blog.rust-lang.org)
        
       | vlovich123 wrote:
       | Very important milestone reached for async programming, not to
       | mention that this is actually a primitive that just happens to be
       | useful for async as well. It's not always needed but when you
       | find yourself needing it, it becomes a major point of friction.
       | Congrats to the team! Excited for dynamic dispatch and capture
       | rules to be coming soon.
        
         | ForkMeOnTinder wrote:
         | It's useful even without async too. Our codebase has some trait
         | methods that return boxed iterators and I'm very much looking
         | forward to switching them to impl Iterator instead.
        
       | adamch wrote:
       | Nice job! I'm glad the team was able to ship a useful subset of
       | the desired functionality. Yes the current implementation
       | shipping next week has limitations, but they're clearly marked,
       | the compiler helpfully explains them and warns you away from some
       | sharp edges.
       | 
       | I'm glad the Rust project is willing to ship useful but limited
       | features quickly, see how people use them, and then iterate and
       | slowly remove the restrictions in the future. I think it'll be
       | more productive than taking another 3 years to solve all the
       | remaining rough edges and problems.
        
         | tempaccount420 wrote:
         | In general, Rust should commit more to stabilizing important
         | features like this. I think because of how easy it is to switch
         | to nightly, there isn't as much pressure to stabilize features
         | as there should be.
        
       | andy_xor_andrew wrote:
       | I'm confused about the example for trait_variant:
       | 
       | ``` #[trait_variant::make(HttpService: Send)] ... ```
       | 
       | In the given example, they create a trait with an async fn that
       | returns HtmlBody, then the trait_variant macro makes a duplicate
       | version of the trait but additionally with a Send bound on the
       | future. Makes sense, because users commonly need the future to be
       | Send.
       | 
       | But... what's the point of having two traits? Could there not
       | just be one trait, with the Send bound? Doesn't the Send bound
       | only _increase_ the places it can be used? Are there scenarios
       | where you need the future to _not_ be Send?
        
         | dureuill wrote:
         | It increases the places it can be used, but it decreases what
         | type can be returned. Not all types can be sent between threads
         | (in particular, types that feature unsynchronized mutability
         | through shared references are not Send). If you executor is
         | local to one thread, it can be a blocker if a trait you use
         | suddenly requires your future to be Send.
        
           | andy_xor_andrew wrote:
           | ah, ok. so this is for scenarios where you, the library
           | author, define the trait, and users will implement it. But
           | since different users have different requirements, you
           | provide two+ trait definitions, so they can pick if they need
           | Send or not.
        
             | Arnavion wrote:
             | Yes. Say the HttpService trait comes from some third-party
             | library. I implement that trait on my server, and then I
             | run my server on a single-threaded executor. (This is
             | analogous to how Iterator is in libstd but I can implement
             | it on my own type and then use that Iterator with a for-
             | loop / combinator in my own code.) My implementation
             | returns Futures that are not Send and my executor does not
             | need them to be Send, so it would be wasteful if the trait
             | required the Futures to be Send.
        
       | andy_xor_andrew wrote:
       | this is so cool :) one other question for async Rust experts...
       | 
       | is it trivial to replace every use of the #[async_trait] with
       | this, once it releases? async_trait was just a macro for adding a
       | bunch of `Box<Pin<dyn Future<Output = BlahBlah> + OtherJunk>` and
       | so on and so on.
       | 
       | Is async_trait entirely obsolete now, or are there still
       | situations where async_trait must be used, where builtin async
       | will not?
       | 
       | edit: if I read one more paragraph I would have seen the answer,
       | it's right there in the post :)
        
         | vlovich123 wrote:
         | For anyone looking for a TLDR, dynamic dispatch is the major
         | missing piece. The only other reason is if you need to support
         | a compiler version < 1.75
        
           | pitaj wrote:
           | I've seen dynamic dispatch mentioned twice. What do you mean?
           | Isn't that just `fn foo() -> Box<dyn Future<...>>`?
        
             | gdcbe wrote:
             | Dynamic dispatch is where you call a method on a Box<dyn T>
             | type, where T is a trait.
             | 
             | Cfr: https://doc.rust-lang.org/1.8.0/book/trait-
             | objects.html
             | 
             | This in contrast to generics which is static.
        
           | gdcbe wrote:
           | Yeah and I was trying to implement their workaround for
           | missing dynamic dispatch in a generic-heavy multithreaded
           | async (tokio, hyper) context..,
           | 
           | Cfr: https://github.com/plabayo/tower-async/pull/12
           | 
           | So far didn't manage to produce a box that works without
           | everything else in my tower async stack having to need send
           | sync trait bounds as well... and of course needing nightly
           | 'call(): Send' return bounds.
           | 
           | Despite that, haven't found a reasonable working solution to
           | support that in 'tower-async'
           | 
           | Might be just me still having to b learn a lot. More then
           | happy to hear feedbacks add help on these matters.
           | 
           | And of course this is a WIP. So fully understand. Respect to
           | the entire async wg team. Onwards and forward.
        
         | WhyNotHugo wrote:
         | `#[async_trait]` does dynamic dispatch, this approach does not.
         | 
         | If you need to use something like `Box<dyn MyTrait>`, then
         | you'll want to continue using `#[async_trait]`.
        
       | monocasa wrote:
       | What's the timeline for allowing async fn with dynamic dispatch?
        
         | duped wrote:
         | You can do                   trait Trait {             fn
         | f(&self) -> futures::BoxFuture<...> {                 let fut =
         | async move { /* ... */ };                 fut.boxed()
         | }         }
         | 
         | (which is essentially what `#[async_trait]` desugars to)
         | 
         | I'm not sure how you would even support dynamic dispatch in the
         | general case without implicitly boxing the return value, which
         | would be deeply unpopular within the Rust community and
         | ecosystem. If we're saying we're ok with implicit allocations
         | for futures then the whole "zero cost abstraction" of
         | async/await seems pointless.
        
           | pitaj wrote:
           | I guess the difference is that that isn't an async function.
           | It's a normal function that returns a boxed future.
        
             | duped wrote:
             | An async function in Rust is a function that returns a
             | future.
        
           | bbatha wrote:
           | Note that an "implicit allocation" doesn't need to be a heap
           | allocation, it just needs to be dynamically sized.
           | Stabilizing the allocator api (which seems tantalizingly
           | close) and someway to pass a non-default allocator to the
           | implicit box _should_ cover zero cost sensitive folks.
           | 
           | Also note that "zero cost abstraction" means that "zero cost"
           | over implementing it by hand there is no way to return an
           | owned dyn sized type in current rust. Syntax that returns a
           | box is inherently zero cost because there's no other way to
           | spell it.
           | 
           | C++ `co_await` also forces an allocation for similar reasons
           | and lets the optimizer get rid of them in the static case
           | which predictably is a mixed bag in practice; at least in
           | rust you can guarantee the static sized version doesn't
           | allocate. That said I don't like forcing an implicit
           | allocation it feels against Rust's ethos and the language
           | will likely have to have better handling of dyn types writ
           | large to get there.
        
             | duped wrote:
             | I agree this is basically an ABI problem. Stripping away
             | the async issue this snippet highlights the issue:
             | trait T {             fn f(&self) -> impl U;         }
             | trait U {             fn g(&self);         }
             | fn h(t: &dyn T) {             // What type is u? how big is
             | it?             let u = t.f();                  // How does
             | this method resolve?             u.g();         }
             | 
             | If `U` is object safe, and `f()` returns something that is
             | `Sized` then it's possible to define a non-heap allocated
             | `dyn U` that is essentially a tuple `(<method table>,
             | <anonymous>)` and this code would desugar to something like
             | fn h(t: &dyn T) {             let u: (<method table>,
             | <anonymous>) = t.f();             u.0.g(&u.1);         }
             | 
             | But that kind of requires a special calling convention for
             | `T::f` when invoked from a `dyn T` that also returns the
             | size of its return type.
             | 
             | edit: Thinking about it some more, what could be done is
             | that functions called from a `dyn Trait` object that are
             | `impl Trait` have a special calling convention where they
             | return everything on the stack. Then callsites take the top
             | of the stack + offset of the method as the function arg and
             | stack + sizeof the method table as their `self` argument
             | and everything else is fine.
             | 
             | I'm not sure if LLVM supports this directly. I know some
             | stack based language implementations and LISPs will do this
             | to avoid writing register allocators so everything just
             | gets dumped on the stack.
             | 
             | Also there's no way (afaik) in Rust to "spell" a non-heap
             | allocated dyn Trait object. There's bare `dyn Trait` but
             | that means something else and is preserved for backwards
             | compatibility, iirc.
        
               | steveklabnik wrote:
               | > Also there's no way (afaik) in Rust to "spell" a non-
               | heap allocated dyn Trait object.
               | 
               | &dyn Trait is fine.                 let i: i32 = 5;
               | let d = &i as &dyn Display;
        
           | JoshTriplett wrote:
           | Here's the current thinking on how to support dynamic
           | dispatch without boxing: https://smallcultfollowing.com/babys
           | teps/blog/2022/03/29/dyn...
        
       | gdcbe wrote:
       | Congratulations to all involved. Huge milestone!
       | 
       | That said, I've been using it seriously on nightly for a while
       | already... and must say its ergonomics are off putting.
       | 
       | For example I maintain https://github.com/plabayo/tower-async (a
       | fork of tower), and on its own it looks to work (except for the
       | boxing and dynamic dispatching others have already discussed).
       | 
       | But once you throw it into the tokio ecosystem, building on top
       | of something like hyper, and you suddenly are back into nightly
       | territory due to having to specify trait bound for your async fn
       | trait methods (eg: call(): Send).
       | 
       | It works, and you can see in my early WIP proxy repo
       | (https://github.com/plabayo/rama). It's not pretty but does
       | work... that said it does put you in a nightly position once
       | again due to having to specify Send/Sync trait bounds of trait
       | async fn methods opaque futures...
       | 
       | In contrast the RFC that would allow me to write 'type Future =
       | impl Future<...' seems to fit a lot better in the existing tokio
       | ecosystem. Just my 2 cents. Anyway, I get its hard work and WIP,
       | so congratulations again and thx for all the work!
       | 
       | (Edit: I do understand that the main source of my issues is due
       | to the combination of writing very generic code and using
       | multithreaded async (via tokio). Still, it's not pretty. But
       | perhaps it's also because I still have a lot to learn on how to
       | use and write this. The latter is my hope)
        
         | kurtbuilds wrote:
         | Can you elaborate on why the `mixing` approach doesn't work?
         | Specifically, if you want send bounds, doing this:
         | trait HttpService: Send {             fn fetch(&self, url: Url)
         | -> impl Future<Output = HtmlBody> + Send;         }
         | impl HttpService for MyService {             async fn
         | fetch(&self, url: Url) -> HtmlBody {                 // This
         | works, as long as `do_fetch(): Send`!
         | self.client.do_fetch(url).await.into_body()             }
         | }
        
           | gdcbe wrote:
           | Of course that works. But only as long as everything is
           | always hardcoded as Send. Which is a known limitation, so no
           | complaints.
           | 
           | Edit: also the problem is about the method async returned
           | future trait bounds, not the Self.
        
         | aliceryhl wrote:
         | It's well known that what is being stabilized today is lacking
         | the Send bounds stuff. In fact, there was a lot of discussion
         | about whether they should completely block this feature until
         | the Send bounds stuff was ready. Ultimately I think it is good
         | that they shipped this part of the feature even though the
         | other part isn't ready yet - Tower isn't able to use this yet,
         | but other crates can.
        
           | gdcbe wrote:
           | Yes Alice fully agreed. I do understand this. I just want to
           | share my experience as a warning to others. As what is
           | shipped is nothing less than amazing. It would be ashamed if
           | this results to disappointment due to wrong expectations, as
           | happened to me. I'm however very grateful for what is already
           | there.
        
       | cchance wrote:
       | impl trait returns is so cool
        
         | gdcbe wrote:
         | Amen to that. Love it sooo much. Looking forward to the day I
         | can use that for associated types. If I can get that for my
         | birthday in May, it would be magical.
        
       | qaq wrote:
       | Congrats! Looks like it was a monumental effort
        
       | Zuiii wrote:
       | Does Rust still require you to rely on external dependencies (or
       | roll out your own async runtime) to run async code?
        
         | steveklabnik wrote:
         | Yes, and that is unlikely to ever change.
        
           | vlovich123 wrote:
           | But it would be nice to standardize the IO / time interfaces
           | so that swapping out runtimes doesn't require writing a
           | custom adapter layer :). I know you know, just saying that's
           | really probably what OP meant.
        
       ___________________________________________________________________
       (page generated 2023-12-21 23:02 UTC)