async-std
An async, std-parity Rust runtime that mirrored fs, net, and task APIs — now deprecated in favor of smol.
Repository Health
Technical Analysis
async-std set out to be an async counterpart to Rust’s standard library, offering task, fs, net, io, and sync modules that mirror std’s naming and shapes so async code could read almost identically to synchronous code. It bundled its own executor (via async-global-executor), reactor (via async-io), and blocking-task pool, so a project could add async-std as a single dependency and get task spawning, non-blocking file and network I/O, timers, and channels without assembling those pieces individually.
The maintainers discontinued the project in 2025, recommending smol as the direct replacement and pointing to the futures-lite/async-io/async-compat ecosystem as sufficient without a full std-mirroring crate. It remains published at v1.13.2 for existing users and legacy codebases, but receives no further feature development.
What You Get
- std-mirrored async modules — fs, net, io, path, process, and sync namespaced and shaped exactly like std’s, minimizing the mental overhaul of switching to async.
- task::spawn / spawn_blocking primitives backed by a bundled async-global-executor thread pool, with task::block_on for bridging into synchronous entry points.
- #[async_std::main] and #[async_std::test] attribute macros (via the attributes feature) that remove boilerplate block_on wiring in main functions and test harnesses.
- Built-in channel, Mutex, RwLock, and Condvar types under sync and channel for message passing and shared-state coordination across tasks.
- Official successor guidance — the README and Cargo.toml point directly to smol as the maintained replacement, reducing migration guesswork for teams currently depending on this crate.
Common Use Cases
- Maintaining a legacy async-std codebase - teams with existing production code built on async-std keep it running on the pinned 1.13.x line while planning a migration.
- Learning async Rust with std-familiar APIs - developers new to async/await use async-std’s std-parity naming to reason about async code without learning a second API vocabulary from scratch.
- Migrating off async-std to smol - teams follow the maintainers’ documented path, swapping async-std’s executor/reactor for smol’s while keeping most call sites unchanged.
- Auditing dependency trees for deprecated crates - security and dependency-health reviews flag async-std when it’s transitively pulled in by older crates so it can be scheduled for replacement.
Under The Hood
Architecture async-std mirrors the std library’s module structure (fs, io, net, task, sync, stream, path, process), providing async counterparts of blocking/threaded APIs by replacing threads with lightweight tasks and blocking calls with Poll-based Future/Stream implementations. Execution begins at task::block_on or the #[async_std::main] macro, which lazily initializes a global Runtime (src/rt/mod.rs) wrapping async_global_executor’s thread pool, configurable via the ASYNC_STD_THREAD_COUNT/ASYNC_STD_THREAD_NAME environment variables. Task spawning routes through task::spawn/spawn_blocking (src/task/spawn.rs, spawn_blocking.rs), handing work either to the async executor or a dedicated blocking-task pool. I/O primitives like fs::File (src/fs/file.rs) wrap std::fs::File behind an UnsafeCell + Arc<Mutex<>> state machine and dispatch actual syscalls to spawn_blocking, exposing them through the crate’s own Read/Write/Seek async traits rather than the futures-io traits directly. This ‘async facade over blocking syscalls executed on a background thread pool’ pattern repeats across fs and process, while networking types (net::TcpStream, UdpSocket) instead delegate to async-io’s reactor for genuinely non-blocking sockets. The core coupling risk is architectural: swapping the executor or reactor requires touching the global RUNTIME static and the many call sites assuming async_global_executor/async-io primitives — exactly the maintenance burden that led maintainers to deprecate the crate in favor of consolidating on smol.
Tech Stack A pure Rust crate (edition 2018, MSRV 1.63) built on a modular optional-feature dependency graph: async-global-executor (2.4) as the default task executor with async-io (2.2) as I/O reactor, futures-lite (2.0) for lightweight future/stream combinators, async-lock/async-channel (3.1/1.8) for synchronization primitives, crossbeam-utils for concurrency helpers, pin-project-lite/pin-utils for safe pinning, kv-log-macro plus log for structured logging, and once_cell for lazy statics. WASM builds swap in wasm-bindgen-futures, gloo-timers, and futures-channel via target-specific dependency blocks. Optional unstable/attributes feature flags gate async-attributes (the #[async_std::main]/#[test] macros) and async-process. Dev-dependencies include femme (logging), rand/rand_xorshift, tempfile, and surf (its sister HTTP client) for examples and tests. CI (.github/workflows/ci.yml) builds against nightly/beta/stable/MSRV Rust across Linux, Windows, and macOS plus a wasm32 target, using cargo check/test via actions-rs.
Code Quality Tests live under tests/ as integration tests (block_on.rs, tcp.rs, udp.rs, mutex.rs, condvar.rs, timeout.rs, and more) exercising the public API end-to-end rather than as unit tests colocated with source, plus a stream test gated behind the unstable feature flag. CI runs cargo check/test across the feature matrix with RUSTFLAGS: -Dwarnings, enforcing a warnings-as-errors bar. Error handling follows idiomatic Rust: async methods return std::io::Result/Result rather than panicking, and internal invariants use .expect() with descriptive messages (e.g. the ARC_TRY_UNWRAP_EXPECT constant in fs/file.rs) instead of silently swallowing errors. Naming and module layout deliberately mirror std (fs::File, net::TcpStream, task::spawn) for immediate familiarity, and the one visible use of unsafe (an UnsafeCell in File) is documented and scoped to interior mutability for lazy blocking-handle acquisition. The project’s own GitHub stats show effectively no recent commit activity, and both the README and Cargo.toml description state the crate is deprecated in favor of smol — code quality here reflects a frozen, no-longer-evolving snapshot rather than an actively hardened one.
API Design async-std’s core developer-facing bet was near-total API parity with std: swapping use std::fs::File for use async_std::fs::File and adding .await was, for years, close to a drop-in migration path, backed by a prelude module (src/prelude.rs) that re-exports the trait-extension methods (ReadExt, WriteExt, StreamExt) needed to call them — lower boilerplate than crates requiring explicit trait imports per method. The #[async_std::main]/#[async_std::test] attribute macros (feature attributes, via async-attributes) remove the need to hand-roll task::block_on(async { .. }) in fn main, a common source of boilerplate in early async runtimes. This ergonomic bet is also the project’s epitaph: maintainers concluded in the README that mirroring std rather than designing a purpose-built async API didn’t durably differentiate the crate once tokio and later smol matured, and they now recommend smol as the direct replacement — so its API-design legacy is more instructive than actionable today.