>*No `tokio`, `rayon`, `hyper`, `async-trait`, `futures`.* No `std::fs`,
I'm not a rust dev but even I kind of notice that tokio is kind of shunned in most projects. Why is that? Is it just bad or what?
It's not really shunned - it's the standard solution for async in Rust - but it's not the right solution for every project, especially if you have specific requirements for how your project's computation should be scheduled. I would guess that Bun is one of those projects, especially as it needs to be able to schedule JS async work itself.
tokio is great and it's pretty performant, but you pay an allocation for every future unless you do some complex organization of your futures.
Source: I worked on Deno, competed directly with Bun on HTTP performance (and won on some metrics).
Edit: and of course I typed future instead of task (aka "spawned future"). Thanks, child commenters below. Much of Deno was built on spawning futures that mapped to promises and doing it as fast as possible. I spent ages writing a future arena to optimize this stuff..
It's an async runtime. The whole async-await flow removes a little bit of scheduling control and adds some forced memory management in order to give you some nicer code in an application case, but if you're trying to build a runtime yourself I think you'd much rather retain control in this case. It's just hard to reason about.
You much rather have this runtime you're building manage task scheduling and allocation and all that. It's the most natural design choice to make.
In pretty much every bit of code I've written both professionally and leisurely I have always used tokio.
However, there are reasons why you might not want to use it:
- You don't need async at all
- You want to own the async execution polling completely
- You want some alternative futures executor like io uring (even though tokio-uring is a thing)
Tokio is a general purpose async runtime. Much the same could probably be said for async-std (except IIRC they do have a barebones reactor for you to build your own on). In general, a general-purpose async runtime will do worse for highly specific tasks than a purpose-built one (especially e.g. NUMA).
I think avoiding async entirely might be a mistake, and I'm not entirely convinced anything better than a general-purpose async runtime might exist for a JS runtime (it itself is general purpose after all).
Avoiding std::fs is fucking bizarre to me: it's completely sync and is a really lightweight abstraction over syscalls.
You shouldn't have to pull in big complex dependencies to do what should be primitive things. Zig is putting a strong and thought-out effort into getting async & parallelism "right" inside the stdlib. I'm honestly not up to speed with where rust is at with it at the moment, but last time I checked it was a bit of a mess.
Async is much harder to work with than sync+threading is. And while threads have more overhead in theory, in practice almost nobody is writing applications at such a scale where that overhead actually matters. So I don't blame them for eschewing async, there's likely no benefit for the project in it.
You try to use it you'll get it. Otherwise it's just words. Like these: rust failed at async.
Async is an anti-pattern but sometimes inexperienced developers don't realize that and will infect your codebase with it.
The answer is in the next sentence: "Bun owns its event loop and syscalls." They clearly want to manage their use of threads explicitly, which is not _unusual_ for systems programming but probably less common. Note that `rayon` is different from most of these in that it has nothing to do with async Rust - it's a tool for spreading computation over a thread pool, very popular in non-async projects, but it would also go against their goals here.