Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

In order to understand why async Rust is difficult to use today, one must understand the background and goals of Rust, in particular Rust's zero-cost abstractions:

The type- and trait system got Rust very, very far, delivering static dispatch and stack allocations by default, while maintaining type safety and a high level of flexibility for a manageable complexity cost. When async Rust was incubating, these goals were upheld dogmatically, and the community set out to preserve all these traits when building async, for better and worse. However, in order to get there, Rust needed to:

- Power-use existing complex features and/or verbose wrappers such `Box`, `Send`, `Arc`, `impl Trait` types and an influx in generic type parameters. (Several of these cause function-coloring issues)

- Introduce new complexity for self-referential structs with `Pin` (and if you dare - check out "pin projection")

- Allow for custom and extensible runtimes (aka executors - responsible for scheduling), including both single- and multithreaded.

The end result is a tremendous technological achievement - essentially all requirements were upheld (to the point where async can be used even in embedded environments). However, for non-experts, it's tremendously complex - which affects almost all users, even those that don't write custom runtimes or advanced combinators.

I'm not the one to judge whether the story of async Rust is a failure, but if it is, there is not a single reason. In broad terms, the project was extremely ambitious and Rust, arguably, wasn't entirely ready for it. A couple of missing features that could possibly have helped:

- Generic Associated Types (currently causes verbose workarounds for combinators, streams etc)

- "first-class" / ergonomic support for state machines (async fns compiles to a state-machine - relevant also for async traits)

- Reliable destructors (`drop` is famously not guaranteed to run in safe Rust, preventing things like borrowing across tasks, which exacerbates the `Arc`-hell)

Additionally, async I/O kernel interfaces were maturing in parallel - e.g. io_uring was released after async Rust was stabilized (afaik), which might have resulted in a different, completion-based design.



Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: