# smol: a re-export runtime for Rust that hands you the pieces

> smol is a small async runtime for Rust that mostly re-exports its own subcrates. It suits engineers who want a working default without adopting a framework, and it is a poor fit if you need the tokio ecosystem's depth.

**smol-rs/smol** — A small and fast async runtime for Rust

- Repository: https://github.com/smol-rs/smol
- Stars: 5,082 · Forks: 198
- Language: Rust
- License: Apache-2.0
- Published: 2026-09-23 · Updated: 2026-09-23 · Language: en
- Canonical page: https://hysenlabs.com/projects/smol-rs-smol

## What smol is for, and who should pick it

smol describes itself as "a small and fast async runtime." The README is blunt about what that means in practice: the crate "simply re-exports other smaller async crates." That single sentence is the whole design, and it decides who benefits. If you want an executor, TCP and UDP sockets, filesystem primitives, locks, channels, and a blocking thread pool available behind one dependency, smol gives you that without asking you to organise your program around a framework.

The audience is narrower than the phrase "async runtime" suggests. The strongest case is a library author. A crate that spawns tasks or performs async I/O has to pick an executor, and picking tokio makes tokio a hard dependency of everyone downstream. Depending on smol instead keeps the surface small and lets the application choose. The second case is an application that needs async networking and timers but not a service framework: a CLI that talks to an HTTP endpoint, a crawler, a small server. The examples directory reflects that scope, with tcp-server, chat-server, web-crawler, linux-inotify and windows-uds files sitting alongside hyper and websocket variants.

What smol is not is a platform. It does not ship a scheduler with work-stealing tuning knobs, a diagnostics layer, or a tracing integration. If your project's shape is already determined by tokio's runtime builder and its ecosystem of instrumented crates, smol is the wrong starting point, and the README acknowledges the overlap by pointing at an adapter rather than pretending the two are interchangeable.

## The mechanism: subcrates, block_on, and Unblock

The architecture is visible in Cargo.toml. The smol package has no internal modules of consequence; its dependencies are async-channel, async-executor, async-fs, async-io, async-lock, async-net, blocking and futures-lite, plus async-process on every target except espidf. Each of those is a separate crate with its own repository and release cadence. smol is the assembly point.

That changes how you read the API. When you call smol::net::TcpStream, you are reaching into async-net. When you call smol::block_on, you are using async-executor. The executor is composable, which is the point of that subcrate: you can build an executor yourself, run it on your own thread, and still use smol's I/O types. The README points at smol-macros for a no-proc-macro async main and multi-threaded executor setup, which tells you the default entry point is deliberately manual.

I/O readiness comes from polling, described in the README as a "portable interface to epoll, kqueue, event ports, and wepoll." So the same code path covers Linux, the BSDs and macOS, and Windows. Timers live in async-io, not in a separate time crate. Blocking work goes to the blocking thread pool, and the README's own example uses Unblock to wrap std::io::stdout so that a synchronous handle can be used from async code. That wrapper is the pattern to remember: smol does not hide blocking APIs, it gives you a way to move them off the reactor thread.

## Installing smol and making a first request

The crate is published on crates.io under the name smol, and the README's Cargo badge links there. Add it the usual way:

```bash
cargo add smol
```

If you prefer to edit the manifest by hand, the package name is smol and the current published version in the repository manifest is 2.0.2. The README's example connects to an HTTP host, writes a raw GET request, and copies the response to standard output. It uses smol::block_on to drive the future to completion on the current thread:

```rust
use smol::{io, net, prelude::*, Unblock};

fn main() -> io::Result<()> {
    smol::block_on(async {
        let mut stream = net::TcpStream::connect("example.com:80").await?;
        let req = b"GET / HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n\r\n";
        stream.write_all(req).await?;

        let mut stdout = Unblock::new(std::io::stdout());
        io::copy(stream, &mut stdout).await?;
        Ok(())
    })
}
```

Run it with cargo run and you should see the raw HTTP response, headers included, printed to your terminal. Two details are worth noticing. The prelude import brings the AsyncWriteExt trait into scope, which is why write_all resolves. And the output handle is wrapped in Unblock rather than used directly, because writing to stdout is a blocking operation and smol wants it on the blocking pool. The README notes there is "a lot more" in the examples directory, which is where you should look next: tcp-server, tls-server and hyper-server are the natural follow-ups.

## Where smol gets awkward

The re-export design has a cost that the README does not spell out. Your public API surface is only as stable as the subcrates underneath, and those version independently. smol 2.0.2 depends on async-io 2.1.0, async-lock 3.0.0, async-fs 2.0.0 and futures-lite 2.0.0, each with its own major version line. If you need a type from async-io directly, you are now tracking two version numbers and hoping they agree. The alternative, using the subcrate alone, means you assemble the runtime yourself.

The second limitation is ecosystem gravity. The README addresses it head-on: "To use tokio-based libraries with smol, apply the async-compat adapter to futures and I/O types." That adapter exists precisely because the two runtimes are not drop-in replacements. Wrapping a tokio-based library works, but it is a bridge, and every bridged dependency is a place where behaviour can diverge from what that library's authors tested. If most of your dependencies are tokio-native, the adapter becomes the architecture, and you have taken on smol's simplicity while keeping tokio's weight.

The third is the MSRV. The README states the Minimum Supported Rust Version is 1.85, and the manifest sets rust-version = "1.85" to match. The policy is described as tentative: the MSRV will not advance past the Rust version in Debian Stable, but it "may be advanced further in the event of a major ecosystem shift or a security vulnerability." For a project pinned to an older toolchain, that is a real constraint, not a footnote. There is also no rollback story documented: the README does not describe how to revert a version bump or what guarantees exist around patch releases.

## smol versus tokio, and versus writing your own executor

The comparison people actually search for is smol against tokio, and the difference is not speed, it is ownership. tokio is a framework: it owns the runtime, the scheduler, the I/O driver, the timer wheel, and a large set of companion crates, and it expects to be the centre of your program. smol owns almost nothing. It re-exports a set of independently versioned crates, and the README's own framing of smol-macros as optional suggests that even the entry point is meant to be replaceable.

That has a concrete consequence for library authors. A crate that depends on tokio forces tokio on every consumer, including consumers running under a different executor. A crate that depends on async-net and async-io can be driven by whatever executor the application already has. This is the argument for smol in one line, and it is the reason the subcrates are published separately at all.

The other alternative is not a runtime but a decision: use async-executor and async-io directly and skip the smol facade. You get the same machinery with one fewer layer and one fewer version to track, at the cost of writing the imports yourself. That is a reasonable choice for a large codebase where the facade adds nothing. It is a worse choice for a small tool or an example, where the single dependency earns its place. There is no benchmark in the README supporting the word "fast," so treat that as a design goal rather than a measured claim.

## Licence, maintenance and what an upgrade costs

smol is dual licensed under Apache-2.0 and MIT, at your option. The manifest records this as "Apache-2.0 OR MIT," and both LICENSE-APACHE and LICENSE-MIT are present at the repository root. The README adds the standard contribution clause: contributions are dual licensed under the same terms unless stated otherwise. For most consumers this is the permissive combination, and it matches what the wider smol-rs family publishes. None of this is legal advice; if your organisation has a licence policy, run the SPDX identifier through it.

The repository is not archived, and the last push was on 2026-08-03. The most recent tagged release is v2.0.2 from 2024-09-07, with v2.0.1 and v2.0.0 before it. So the release cadence is slow and the commit activity is more recent than the last tag, which is normal for a project whose real work happens in the subcrates. When you upgrade smol, you are mostly upgrading its dependency versions, and the changelog in CHANGELOG.md is the place to check what moved. The Cargo.toml carries a comment telling maintainers to update CHANGELOG.md and create a v2.x.y tag when publishing, which is the only release process the material documents.

Upgrade cost is therefore low in the common case and unpredictable at the edges. A patch bump that pulls a new async-io minor can change I/O behaviour you depend on. Pin your versions, read the changelog, and treat a smol upgrade as an upgrade of its whole dependency set rather than of a single crate.

## Conclusion

Adopt smol if you want a working async runtime without committing to a framework, or if you are writing a library that should not force an executor on its users. Do not adopt it if you need the breadth of tokio-specific crates, or if you depend on a Rust toolchain older than 1.85, which the README names as the MSRV. Before starting, verify that the subcrates you actually need are re-exported by the version of smol you pin, and check whether any tokio-based dependency in your tree can be bridged with async-compat rather than replaced.

## FAQ

### What is smol in Rust?

smol is an async runtime crate that re-exports a set of smaller crates covering executors, channels, filesystem primitives, I/O, locks and networking. The README describes it as "a small and fast async runtime" and states that the crate simply re-exports other smaller async crates.

### How do I install smol and run a first example?

Add it with cargo add smol, then drive your future with smol::block_on. The README's example connects a TcpStream to example.com:80, writes a raw GET request, and copies the response to stdout through an Unblock wrapper.

### Can smol use tokio-based libraries?

Yes, through an adapter rather than natively. The README says to apply the async-compat adapter to futures and I/O types when using tokio-based libraries with smol.

### What Rust version does smol require?

The README sets the Minimum Supported Rust Version at 1.85, and the manifest matches with rust-version = "1.85". The policy is tentative: the MSRV will not advance past the Rust version in Debian Stable, but may advance further after a major ecosystem shift or a security vulnerability.

## Sources

- [Issues](https://github.com/smol-rs/smol/issues)
- [License: Apache-2.0](https://github.com/smol-rs/smol/blob/master/LICENSE)
- [README](https://github.com/smol-rs/smol/blob/master/README.md)
- [Releases](https://github.com/smol-rs/smol/releases)
- [smol-rs/smol on GitHub](https://github.com/smol-rs/smol)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/smol-rs-smol
