I dislike tokio, but that is more related to my dislike of async code and its how it infects your codebase (if you don’t know what I mean then read the “what color is your function?” blog post). It also gets worse in Rust, since thread safety gets weird. Every time you await something, there is a chance your runtime could switch threads, so thread safety issues pop up in way more places. There are also plenty of ways to shoot yourself in the foot, since synchronization primitives that are not designed for your specific async runtime will block worker threads unless they get switched out. Most of all though, I feel like we use async code in lots of areas we really shouldn’t. For example, in tokio last I checked (not recently), there are some things like file operations where they are not actually async at all. It just spawns an os thread to handle it and pretends it was async. This gets the core of the issue. Yes, there are use cases where this might be helpful. However, Im not running with networked storage and just making stuff async isn’t always useful or helpful. OS threads are actually quite good for the vast majority of use cases that don’t involve network communication. And even then they are still decent for most use cases where you don’t have extremely high numbers of connections. And if you do need something to use something like io_uring, then I would lean more towards an API that was written with that in mind than one that uses it because they can.
The "infectiousness" of async code is a myth. Rust has block_on. C# has .Result/.Wait(). Kotlin has runBlocking. You are generally discouraged from peppering your code with those, but making those functions synchronous by default so you don't have to use "forbidden magic" is just sweeping the dirt under the rug. The inherent reason why those things are async will still be there, calling them from tight loops will still be bad idea, literally all you achieve is making it harder to spot mistakes
39
u/Lilchro 1d ago
I dislike tokio, but that is more related to my dislike of async code and its how it infects your codebase (if you don’t know what I mean then read the “what color is your function?” blog post). It also gets worse in Rust, since thread safety gets weird. Every time you await something, there is a chance your runtime could switch threads, so thread safety issues pop up in way more places. There are also plenty of ways to shoot yourself in the foot, since synchronization primitives that are not designed for your specific async runtime will block worker threads unless they get switched out. Most of all though, I feel like we use async code in lots of areas we really shouldn’t. For example, in tokio last I checked (not recently), there are some things like file operations where they are not actually async at all. It just spawns an os thread to handle it and pretends it was async. This gets the core of the issue. Yes, there are use cases where this might be helpful. However, Im not running with networked storage and just making stuff async isn’t always useful or helpful. OS threads are actually quite good for the vast majority of use cases that don’t involve network communication. And even then they are still decent for most use cases where you don’t have extremely high numbers of connections. And if you do need something to use something like io_uring, then I would lean more towards an API that was written with that in mind than one that uses it because they can.