Live data from Hacker News

Async-await on stable Rust

blog.rust-lang.org

91–100 of 392 posts

Re: Async-await on stable Rust

#91
post #60

Earlier quoted context omitted.

In Rust, you can use a future adapter that does this: futures::join!(asyncTaskA(), asyncTaskB()).await See the join macro of futures[0]. The way it works is, it will create a future that, when polled, will call the underlying poll function of all three futures, saving the eventual result into a tuple. This will allow making progress on all three futures at the same time. [0] https://docs.rs/futures/0.3.0/futures/macr…

I don't like this at all. Having to rely on futures::join! means that I don't have the flexibility to control the execution of these things unless Rust adds that specific utility, right? In JS, for example, the `bluebird` library is a third party utility for managing execution of functions. You can do things like const results = await Promise.map(users, user => saveUserToDBAsync(user), { concurrency: 5}); And I pass…

The async map you laid out above could be accomplished with a `Stream` in async Rust. You can turn the array into a `Stream`, have a map operation to return a future, and then use the `buffered` method to run N at once:

https://docs.rs/futures/0.3.0/futures/stream/trait.StreamExt...

Not only does the `futures` crate provide most things you'd ever want, it also has no special treatment – you can implements your own combinators in the same way that `futures` implements them if you need something off of the beaten path.

Re: Async-await on stable Rust

#92
post #60

Earlier quoted context omitted.

In Rust, you can use a future adapter that does this: futures::join!(asyncTaskA(), asyncTaskB()).await See the join macro of futures[0]. The way it works is, it will create a future that, when polled, will call the underlying poll function of all three futures, saving the eventual result into a tuple. This will allow making progress on all three futures at the same time. [0] https://docs.rs/futures/0.3.0/futures/macr…

I don't like this at all. Having to rely on futures::join! means that I don't have the flexibility to control the execution of these things unless Rust adds that specific utility, right? In JS, for example, the `bluebird` library is a third party utility for managing execution of functions. You can do things like const results = await Promise.map(users, user => saveUserToDBAsync(user), { concurrency: 5}); And I pass…

Fortunately, `futures::join!` isn't provided by Rust- it's provided by library code. I am not aware (off the top of my head) of an existing equivalent to your `Promise.map` example, but it is also implementable in userspace in Rust.

The primitive operation provided by a Rust `Future` is `poll`. Calling `some_future.poll(waker)` advances it forward if possible, and stashes `waker` somewhere for it to be signaled when `some_future` is ready to run again.

So the implementation of `join` is fairly straightforward: It constructs a new future wrapping its arguments, which when polled itself, re-polls each of them with the same waker it was passed.

There are also more elaborate schemes- e.g. `FuturesUnordered` uses a separate waker for each sub-future, so it can handle larger numbers of them at some coordination cost.

Re: Async-await on stable Rust

#93
post #65

Earlier quoted context omitted.

JavaScript automatically starts the tasks and this is bad design IMHO. One loses referential transparency and the ability to run the workflow with different schedulers. It looks like Rust has done a better job.

JavaScript does not automatically start the tasks. It executes the function if you...execute the function. If you just want to pass around something with deferred execution, you can just pass the function around, or wrap it in a closure.

If I understand correctly, applying a JavaScript Async function is effectful and starts the background tasks. A better design is for the async function to yield an Async computation when applied. These computations can then be composed and passed around with full referential transparency. Only when we run the final composed Async computation, should the effects happen (threads running our computation). This is how the original F# asynchronous workflows were designed, which predate the C# and JavaScript implementations. Thankfully Rust works this way too!

Re: Async-await on stable Rust

#94
post #79

Earlier quoted context omitted.

The join macro is also implemented “in user space”.

Thank you, so what is the syntax that is used in order to execute without awaiting? Do you create a thread for each?

You do not create a thread for each- that would defeat most of the purpose of futures.

Instead you call `Future::poll`, which runs a future until it blocks again, and provide it a way to signal when it is ready.

That signal would be handed off to an event loop (which tracks things executing on other hardware like network or disk controllers) or another part of the program (which will be scheduled eventually).

Re: Async-await on stable Rust

#95

Earlier quoted context omitted.

> The point is that Rust's borrow checker can't reason about lifetimes very well over function boundaries. It can reason about coarse things that are expressable in the type language, but everything more nuanced than that, such as reasoning about how control flow affects the lifetimes is limited to inside function bodies. BTW this is a big pain point for me (unrelated to async). Code like this: let ref = &mut self.fi…

Couldn't you just pass in the (other) borrowed field as an argument for the function? If you need it to work without adding the argument when called outside of the class, you could overload it with a version that borrows the field and passes it to the version that takes the field as an argument, right? I'm newish to Rust, so this is just an intuitive guess. Please let me know if I'm wrong.

Yes, that's the usual workaround.

Re: Async-await on stable Rust

#96
post #72

Earlier quoted context omitted.

Have you ever heard of Esterel or Céu? They follow the synchronous concurrency paradigm, which apparently has specific trade-offs that give it great advantages on embedded (IIRC the memory overhead per Céu "trail" is much lower than for async threads (in the order of bytes ), fibers or whatnot, but computationally it scales worse with the nr of trails). Céu is the more recent one of the two and is a research language…

I also think async (the paradigm) is kind of weird in rust world. I agree with https://journal.stuffwithstuff.com/2015/02/01/what-color-is-... .

All 5 of his points seem to be 2015 Javascript only. Some of them don't even apply to modern Javascript; I don't see any that apply to rust.

Re: Async-await on stable Rust

#97
post #30

Isn't it kind of a poor design choice that Rust will not actually begin execution of the function until `.await` is called? If I didn't want to execute the function yet, I wouldn't have invoked it. Awaiting is a completely different concept than invoking, why overload it? If you want to defer execution of a promise until you await it, you can always do that, but this paradigm forces you to do that. The problem is the…

> Isn't it kind of a poor design choice that Rust will not actually begin execution of the function until `.await` is called?

Begin execution where?

If every future started executing immediately on a global event loop, that event loop would need to allocate space for every future on the heap. A heap allocation for every future is exactly the sort of overhead that Rust is trying so carefully to avoid. With Rust futures, you can have a large call tree of async functions calling other async functions. Each one of those returns a future, and those futures get cobbled together by the compiler into a single giant future of a statically known size. Once that state machine object is assembled, you can make a single heap allocation to put it on your event loop. Or if you're going to block the current thread waiting on it, depending on what runtime library you're using, you might even get away with zero heap allocations.

This sort of thing is also why Rust's futures are poll-based, rather than using callbacks. Callbacks would force everything to be heap allocated, and would work poorly with lifetimes and ownership in general.

Re: Async-await on stable Rust

#98
This is a big improvement, however this is still explicit/userland asynchronous programming: If anything down the callstack is synchronous, it blocks everything. This requires every components of a program, including every dependency, to be specifically designed for this kind of concurency.

Async I/O gives awesome performance, but further abstractions would make it easier and less risky to use. Designing everything around the fact that a program uses async I/O, including things that have nothing to do with I/O, is crazy.

Programming languages have the power to implement concurrency patterns that offer the same kind of performances, without the hassle.

Re: Async-await on stable Rust

#99
For JavaScript developers expecting to jump over to Rust and be productive now that async/await is stable:

I'm pretty sure the state of affairs for async programming is still a bit "different" in Rust land. Don't you need to spawn async tasks into an executor, etc.?

Coming from JavaScript, the built in event-loop handles all of that. In Rust, the "event loop" so to speak is typically a third party library/package, not something provided by the language/standard itself.

Re: Async-await on stable Rust

#100

I’ve been playing with async await in a polar opposite vertical than its typical use case (high tps web backends) and believe this was the missing piece to further unlock great ergonomic and productivity gains for system development: embedded no_std. Async/await lets you write non-blocking, single-threaded but highly interweaved firmware/apps in allocation-free, single-threaded environments (bare-metal programming wi…

Have you ever heard of Esterel or Céu? They follow the synchronous concurrency paradigm, which apparently has specific trade-offs that give it great advantages on embedded (IIRC the memory overhead per Céu "trail" is much lower than for async threads (in the order of bytes ), fibers or whatnot, but computationally it scales worse with the nr of trails). Céu is the more recent one of the two and is a research language…

It's not the same, but Rust async/await tasks are also in the order of bytes, and you can get similar "structured concurrency"-like control flow with things like `futures::join!` or `futures::select!`.

Ceu looks very neat, I suspect (having not read much about it yet) that async codebases could take a lot of inspiration from it already.

Post reply on HN