Live data from Hacker News

How to think about async/await in Rust

cliffle.com

71–80 of 268 posts

Re: How to think about async/await in Rust

#71
post #14

Earlier quoted context omitted.

Asynchronous programming is a great fit for IO-driven programs, because modern IO is inherently asynchronous. This is clearly true for networking, but even for disk IO, generally commands are sent to the disks and results come back later. Another thing that’s asynchronous is user input, and that’s why JS has it. As for threading vs. explicit yielding (e.g. coroutines), I’d say it’s a matter of taste. I generally pref…

That's not how an operating system models disk access though. You synchronously write to the kernel cache, and the kernel eventually gets those written to disk. Wanting to do asynchronous I/O to disk is only useful if you're aiming to bypass the cache. In practice it is very hard to reach higher performance when doing that though.

I was referring to the fact that interaction with the disk itself is asynchronous. Indeed, the interface provided by a kernel for files is synchronous, and for most cases, that's what programmers probably want.

But I also think the interest in things like io_uring in Linux reflect that people are open to asynchronous file IO, since the kernel is doing asynchronous work internally. To be honest, I don't know much about io_uring though - I haven't used it for anything serious.

There's no perfect choice (as always) -- After all, for extremely high-performance scenarios, people avoid the async nature of IO entirely, and dedicate a thread to busy-looping and polling for readiness. That's what DPDK does for networking. And I think for io_uring and other Linux disk interfaces have options to use polling internally.

Re: How to think about async/await in Rust

#72
post #14

Earlier quoted context omitted.

Asynchronous programming is a great fit for IO-driven programs, because modern IO is inherently asynchronous. This is clearly true for networking, but even for disk IO, generally commands are sent to the disks and results come back later. Another thing that’s asynchronous is user input, and that’s why JS has it. As for threading vs. explicit yielding (e.g. coroutines), I’d say it’s a matter of taste. I generally pref…

That's not how an operating system models disk access though. You synchronously write to the kernel cache, and the kernel eventually gets those written to disk. Wanting to do asynchronous I/O to disk is only useful if you're aiming to bypass the cache. In practice it is very hard to reach higher performance when doing that though.

[deleted]

Re: How to think about async/await in Rust

#73
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…

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, and then the task group waits for all tasks to complete (successfully, with exception, or cancelled). It's extremely nice and easy to write reliable programs.

In principle, I think many of these ideas could be applied to threaded IO. But I haven't seen it done in practice.

Re: How to think about async/await in Rust

#74
post #41
post #15

I think Go got it right by inverting the logic around async/await. In Go you have to explicitly state that a function is to run in the background via "go fn(...)". This makes it much clearer that this code will execute concurrently. In the async/await world you can't tell by looking at a function call if it will block until it's done. Forgot an await? No compile error but your program might behave in weird ways. This…

> Haven't done too much async Rust yet but I don't think it solved this issue from what I've seen. In Rust an async function is really just a const fn that synchronously only constructs and returns a state machine struct that implements the Future trait. So async fn foo(x: i32) { } essentially desugars to const fn foo(x: i32) -> FooFuture { FooFuture { x } } struct FooFuture { x: i32 } // technically it's an enum mod…

It's not const (const fn has a very specfic meaning in Rust), but other than that you're correct.

Re: How to think about async/await in Rust

#75

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…

> We left behind that paradigm in Operating Systems decades ago, and with good reason. I'm curious, what reason? I grew up on Python and C#, and only know async/await, never done real threading (C# async is threading and coroutines under the hood, Python is just coroutines, single-threaded). I find that way of writing code very elegant, as one can encode points of blocking/switching explicitly. A bit like encoding lo…

The reason we left that paradigm in _Operating Systems_ is that OS's are supposed to be resilient. A single buggy app could easily freeze/crash the whole Windows 3.1 system, because the system has the naive assumption that all the apps are benevolent, bug-free, and happily co-operate with time-sharing. Try the same in Windows 2000; you can't, because the system is pre-emptive and forcibly ends the time slots of apps that don't yield. (And possibly kills the app; "MyApp isn't responding" etc.)

However, that same reason doesn't apply within a single app, because a single app by a single author _can_ safely co-operate with itself. So co-operative time sharing can work and make sense within single app.

Re: How to think about async/await in Rust

#76
post #55
post #3

Earlier quoted context omitted.

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…

async/await doesn't entirely remove the need for mutexes and locks. We still need them if we have multiple coroutines using a shared resource across multiple yield points.

> We still need them if we have multiple coroutines using a shared resource across multiple yield points.

We still need them if we have multiple parallel tasks (coroutines spawned non-locally) using a shared resource across multiple yield points.

As long as the accesses to the shared variable are separated in time, sharing is fine.

This is correct code:

        let mut foo = 1;
        async { foo += 1 }.await;
        foo += 1;
        println!("{foo}");
See - a shared variable used across multiple yield points. Another (more useful) example I showed below in another post with `select!`.

Re: How to think about async/await in Rust

#77

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…

My opinion is the opposite, to the point I would argue that anyone advocating for multithreading for reasons other than executing things in parallel on different cores is extremely dangerous and shouldn't be allowed anywhere near a serious codebase.

Rust pretty much alleviates these dangers. At least no memory safety bugs because of multithreading. Logic bugs are still possible, of course, but the channel API and the scoped thread API in the standard library do help with those.

Re: How to think about async/await in Rust

#78

Earlier quoted context omitted.

It began in C# in 2012 - 5 years before JavaScript. And C# does threads. And made its way to JavaScript via Typescript (whose creator also created C#).

Maybe I'm misunderstanding what you mean by "It began in C#", but F# introduced async about five years before C# 5 was released.

async in F# is not a language feature, it’s a library that leverages F# computation expressions (monads).

It’s also possible to do async-like behaviour - without the async/await language feature - in C# using LINQ; so you could argue C# has had the capability (like F#) since LINQ was released.

But, I believe C# was the first mainstream language to implement the async/await method-splitting coroutines state-machine (as a language feature)

Re: How to think about async/await in Rust

#79
post #71

Earlier quoted context omitted.

That's not how an operating system models disk access though. You synchronously write to the kernel cache, and the kernel eventually gets those written to disk. Wanting to do asynchronous I/O to disk is only useful if you're aiming to bypass the cache. In practice it is very hard to reach higher performance when doing that though.

I was referring to the fact that interaction with the disk itself is asynchronous. Indeed, the interface provided by a kernel for files is synchronous, and for most cases, that's what programmers probably want. But I also think the interest in things like io_uring in Linux reflect that people are open to asynchronous file IO, since the kernel is doing asynchronous work internally. To be honest, I don't know much abou…

Networking and disks are inherently entirely different.

Pretending they're the same under some generic I/O concept only goes so far.

Re: How to think about async/await in Rust

#80

Earlier quoted context omitted.

My opinion is the opposite, to the point I would argue that anyone advocating for multithreading for reasons other than executing things in parallel on different cores is extremely dangerous and shouldn't be allowed anywhere near a serious codebase.

Rust pretty much alleviates these dangers. At least no memory safety bugs because of multithreading. Logic bugs are still possible, of course, but the channel API and the scoped thread API in the standard library do help with those.

That is not true at all, "fearless concurrency" gives no valuable guarantees at all and is widely seen as one of the worst concurrency models in literature.
Post reply on HN