>*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.

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.

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..

Do you mean allocate on every task?

You only allocate on box futures, which are much more rare than naked futures - generally only used where object safety (essentially dyn support) is required. Even then some workarounds exist.

Edit: and tasks.

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.

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.

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.

my guess is they want to do AI/O as part of their event loop explicitly, and blocking a thread in a syscall waiting for an IOP (ala std::fs) isn't the vibe.

Ah good point, complete brain fart on my part.

`tokio`, and Rust `futures` in general, are perfectly fine for typical applications.

But as soon as you need something that doesn’t fit neatly into the abstractions they provide, even something as seemingly simple as proactively reusing or cancelling sessions, things quickly become extremely complicated, inefficient, and unreliable.

For high-performance servers, where you really care about raw performance, DoS resistance, and taking advantage of modern kernel features, these abstractions can become a major limitation.

It’s a bit like using an ORM that gives you no easy way to send raw SQL queries. It works fine for common cases, even if it’s not always optimal. But when you really want to take advantage of what the database can do, you usually avoid the ORM.

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.

Please explain.