Live data from Hacker News

How to think about async/await in Rust

cliffle.com

91–100 of 268 posts

Re: How to think about async/await in Rust

#91
post #4

The article shows a great example of how to implement a state machine with internal delays (do something, wait for a defined time, do something else), which is very useful in a driver or embedded context where you often just have to wait for an external device to be ready. However, it doesn't really address how you'd construct a state machine with an external tick. It's pretty common to have a state machine called at…

What is the difference between „call state machine nextStep() with a fixed timer“ vs „call async fns with a delay“?

Re: How to think about async/await in Rust

#92
post #34

Earlier quoted context omitted.

> foo(); Only if you know that foo is an async function. You can't tell by the function call itelf. > warning: unused implementer of `futures::Future` that must be used Interesting, I haven't seen this warning in the Rust codebase I worked a little with. I'll have to check the compiler settings. Anyways wouldn't it make sense to actually throw an error instead of just a warning? > Additionally there are certain thing…

> Only if you know that foo is an async function. You can't tell by the function call itelf. That's fair point, but traditionally you don't use blocking functions in async contexts at all. It is fairly easy to lint for by prohibiting some inherently blocking calls eg.g std::io, although they might sneak in through some third-party dependency. This doesn't have an easy solution because Rust is a general purpose langua…

You can't change the async/await rules of Rust anymore. I get that. But if it started like I described from the beginning I don't see why that wouldn't work. It's just a question of syntax. Someone adding a blocking call 5 layers down wouldn't be any different than someone adding an "await foo()" right now. Code would still compile fine. As long as everything follows the same rules. Can't mix them obviously.

Re: How to think about async/await in Rust

#93
post #11

Earlier quoted context omitted.

One advantage of async/await is that its easier to cancel things. For example, this leads to the design pattern where you have multiple futures and you want to select the one that finishes first and cancel the rest. In regular threaded programming, cancellation is a bit more painful as you need to have some type of cancellation token used each time the thread waits for something. This a) is more verbose and b) can le…

> One advantage of async/await is that its easier to cancel things. For example, this leads to the design pattern where you have multiple futures and you want to select the one that finishes first and cancel the rest. > In regular threaded programming, cancellation is a bit more painful as you need to have some type of cancellation token used each time the thread waits for something. This a) is more verbose and b) ca…

Since rust follows RAII, any and all resources allocated in the context should be deallocated when their destructor (the drop trait) is called. The unfortunate exception to this are resources which require an async call to deallocate properly. Though this can be worked around and there is work being done to fix this properly.

Re: How to think about async/await in Rust

#94
post #48

Earlier quoted context omitted.

Look at this program: val another_path = await readFile(path); val data = await readFile(another_path); console.log(data.length); How would you do that using threads?

uh, open a thread that does that and `join` on it waiting for it to complete, but why would you need that in your example?

Which, ironically, is what ought to have been done in the example using join!(a,b).await.

Re: How to think about async/await in Rust

#95

Earlier quoted context omitted.

> One advantage of async/await is that its easier to cancel things. For example, this leads to the design pattern where you have multiple futures and you want to select the one that finishes first and cancel the rest. > In regular threaded programming, cancellation is a bit more painful as you need to have some type of cancellation token used each time the thread waits for something. This a) is more verbose and b) ca…

I think the comment was about async in general, not just Rust (although that's the topic of OP). In Python, cancellation causes an exception to be injected at the await site, which allows it to clean up whatever resources it likes (even if that means making other async calls). If you use Trio or the new TaskGroup in asyncio (inspired by Trio) then an exception leaking out of one task causes the others to be cancelled…

> In principle, I think many of these ideas could be applied to threaded IO.

That's how POSIX deferred [1] cancellation works. An uncatchable exception is thrown from blocking calls if a thread is requested to terminate. As POSIX is C centered, you can imagine that handling exceptions was never popular, but it should work fine in C++. For some reasons it wasn't added to std::thread though.

[1] there is also async cancellation, but friends do not let friends use PTHREAD_CANCEL_ASYNCHRONOUS.

Re: How to think about async/await in Rust

#96

Earlier quoted context omitted.

Point taken. What about this pattern (pseudo code, obviously it would require e.g. adding some code for tracking how much data there is in the buffer or breaking the loop on EOF, but it illustrates the point): mut buffer: &[u8] = ...; loop { select! { _ = stream.readable() => stream.read(&mut buffer), _ = stream.writable() => stream.write(&mut buffer), } }

One you add enough tracking meta data to to know how much there is in the buffer, you literally have implemented an SPSC queue.

Well, not really, because async/await guarantees I don't have to deal with the problem of producer adding data at the same time as consumer is removing the data in this case. In a proper SPSC queue some degree of synchronization is needed.

Re: How to think about async/await in Rust

#97
post #3

Not specific to rust, but I think asynchronous programming in general is a hype. It didn't start because it is so awesome, it started because JS can't do parallel any other way. That's the long and short of it. People wanted to use JS in the backend for some reason. The backend requires concurrency. JS cannot do concurrency. Enter the event loop. Then enter some syntactic sugar for the event loop. And since JS is pop…

Your comment seems to be conflating concurrency with parallelism. JS doesn't have any language-level abstractions for parallelism (async or not) but you do have Web Workers[0] and process forking (depending on runtime) to get actual parallel programming. JS async deals with concurrency, not parallelism. Threads are the opposite: They are interfaces for parallel programming and their use is orthogonal to how your appl…

> Your comment seems to be conflating concurrency with parallelism.

No, it really doesn't. I mention both concurrency and parallelism, and their main difference.

> Threads are the opposite: They are interfaces for parallel programming

No, they are not. Threads can do both. When waiting for an i/o bound operation, a thread can simply sleep. Added bonus: A thread basd implementation supports io bound concurrency and cpu bound parallelism using the exact same principle, and letting the kernel/runtime take care of the details.

> are you telling me you never write a mutex or implement locking logic?

Pretty sure I never said that. As for what I prefer to debug: Most Mutex-based synchronicity tasks that come up in practice are easy. And if "complex deadlock" does occur, it's usually pretty clear what resource was locked. Debugging that is just a question of going over all callers that access that resource.

And as mentioned before, all that code is synchronous. So each one of them is easy to reason about.

So yea, all in all, I prefer debugging problems arising from deadlocks over wading through callback-hell. By a huge margin.

Oh, and all that is before we even talk about using CSP as an approach to synchronizing threads, which makes it both harder to mess up, and again easier to reason about.

Re: How to think about async/await in Rust

#98

Thanks for this article. I feel the goal of a tool should be to make common patterns easy to represent: so "sprinkling async everywhere" only doesn't work because of some common desirable pattern not being easily representable in modern languages and gotchas of the languages. I have a lightweight thread scheduler written in C and I communicate between threads with a lockless ringbuffer. IO threads do IO. I like the i…

Maybe take a look at pi-calculus and session types if you're not familiar, the notation you're describing sounds very similar/related to that

Re: How to think about async/await in Rust

#99

Earlier quoted context omitted.

One you add enough tracking meta data to to know how much there is in the buffer, you literally have implemented an SPSC queue.

Well, not really, because async/await guarantees I don't have to deal with the problem of producer adding data at the same time as consumer is removing the data in this case. In a proper SPSC queue some degree of synchronization is needed.

You stop adding data when the queue is full, you stop popping when it is empty. You need the exact same synchronisation for async, just different primitives.

Re: How to think about async/await in Rust

#100
post #50
post #26

Earlier quoted context omitted.

The issue arises if you don't use the returned value. Lets say there's a function "async fn saveToDisk()". You call this function before you exit the program. Now if you forget to use await on it, your program will exit without having saved the data to disk.

In any sensible API, saveToDisk would return an error status (a Result type in Rust). If you don't check for errors, then probably you didn't care of the data was actually saved or not.

Futures in Rust are annotated with the #[must_use] attribute [1], same as the Result type [2].

This means the compiler will emit a warning (can be upgraded to an error) if you forget to await a future even if it doesn't return anything.

[1]: https://doc.rust-lang.org/nightly/src/core/future/future.rs.... [2]: https://doc.rust-lang.org/nightly/src/core/result.rs.html#49...

Post reply on HN