Library / SDK
tokio-rs/mio avatar
tokio-rs/mio

Mio: the non-blocking I/O layer under Rust's async runtimes

Metal I/O library for Rust.

7,110 stars876 forksRustMIT

At a glance

What is it?
Mio is a low-level Rust library that wraps epoll, kqueue and IOCP behind a single Poll API. It is the right tool for runtime authors and protocol implementers, and the wrong one for application code that just wants async/await.
Who is it for?
Adopt Mio when you are writing a runtime, a protocol implementation, or a single-threaded event loop where you want to control every syscall and allocation. Do not adopt it if you want async/await, timers, or a thread pool: the README lists those as non-goals, and Tokio is the project's own recommendation for an easier start.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 27 days ago.
What is it written in?
Mainly Rust, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 29, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What Mio solves, and who actually needs it

Rust's standard library gives you blocking sockets. Call read on a TcpStream and the thread parks until bytes arrive. That is fine for a handful of connections and hopeless for ten thousand. The operating systems solved this decades ago with readiness notification: epoll on Linux, kqueue on the BSDs and macOS, IOCP on Windows. Each has its own API, its own registration model, and its own edge cases. Mio is the crate that hides that difference behind one type, Poll, and one loop shape.

The README is blunt about the audience: "This is a low level library, if you are looking for something easier to get started with, see Tokio." That sentence is the whole positioning. Mio is for the people who build the thing everyone else uses. Tokio itself sits on top of Mio. So does any runtime that wants to avoid reimplementing epoll bindings. If you are writing an HTTP server from scratch, a database wire protocol, a proxy, or a custom reactor, Mio is the layer you want. If you are writing a web service, you want Tokio, async-std, or smol, and you will never call Mio directly.

How Poll, Registry and Token fit together

The mechanism is a registration loop. You create a Poll, which owns the OS event queue. You register each socket with poll.registry(), passing a Token that you choose. The token is not a file descriptor and not a pointer; it is an opaque u64 you will get back, and its only job is to tell you which socket an event belongs to. Then you call poll.poll(&mut events, timeout). That call blocks until the OS reports readiness for one or more registered sources, and fills the Events buffer you supplied.

Two details matter in practice. First, the Events buffer is allocated once with Events::with_capacity and reused across iterations, which is where the README's "zero allocations at runtime" claim comes from: the event loop itself does not allocate per iteration. Second, readiness is a hint. The README's own example says "We can (likely) read from the socket without blocking" and "We can (likely) write." That word likely is doing real work. A readiness notification means the operation would probably not block, not that it will succeed. Non-blocking sockets can still return WouldBlock, and a correct program handles that by re-registering interest rather than treating it as an error.

Interest is explicit. You tell the registry whether you care about Interest::READABLE, Interest::WRITABLE, or both. Registering for writability on a socket that is almost always writable is a common way to spin the event loop at full CPU, so the interest set is a performance decision, not a formality.

Installing Mio and running a first event loop

Mio is published on crates.io. The README gives the dependency line for the current major version. Note that the default feature set is deliberately thin: Cargo.toml declares default = ["log"], and the README states that features = ["os-poll", "net"] must be specified for the TCP example. Without os-poll there is no Poll type, and without net there is no mio::net module.

toml
[dependencies]
mio = { version = "1", features = ["os-poll", "net"] }

With that in place, the README's quick introduction builds a server and a client in the same process, registers both, and loops until the client sees the server close the connection. The essentials are the three constants and the loop:

rust
use mio::net::{TcpListener, TcpStream};
use mio::{Events, Interest, Poll, Token};

const SERVER: Token = Token(0);
const CLIENT: Token = Token(1);

let mut poll = Poll::new()?;
let mut events = Events::with_capacity(128);

let addr = "127.0.0.1:13265".parse()?;
let mut server = TcpListener::bind(addr)?;
poll.registry()
    .register(&mut server, SERVER, Interest::READABLE)?;

What you should see when you run the README example is a short-lived program: the server accepts one connection and drops it immediately, the client receives the EOF, and the loop returns Ok(()). The port 13265 is the one the README uses, so avoid it if something else on your machine has claimed it.

The repository also ships runnable examples under examples/: tcp_server.rs, udp_server.rs, and tcp_listenfd_server.rs. Those are the fastest way to see a realistic accept-and-echo loop rather than the minimal one in the README.

What Mio deliberately leaves out

The README has a non-goals section, and it is unusually honest. File operations are omitted. Thread pools and multi-threaded event loops are omitted. Timers are omitted. Each of those omissions is a design decision with a cost.

No timers means you cannot express "close this idle connection after 30 seconds" inside Mio. You have to compute the next deadline yourself, pass it as the timeout argument to poll.poll, and keep a data structure of pending deadlines. Every Mio-based runtime ends up writing that scheduler. No thread pool means the reactor is single-threaded by construction; scaling to multiple cores is your problem, and the usual answer is one Poll per thread with a handoff channel, which is exactly the machinery Tokio provides and Mio does not.

There is a second limitation that is easy to miss. Mio picks one implementation per platform, described in the README as the "best" one for Mio's use case. When that choice is wrong for you, the escape hatch is a set of cfg flags, mio_unsupported_force_poll_poll for a poll(2)-based Poll and mio_unsupported_force_waker_pipe for a pipe(2)-based Waker. The README is explicit: "Mio does not officially supports this," and the flags "may disappear in the future." Using them means accepting an unsupported configuration. That is a real constraint, not a footnote.

Mio versus Tokio: same problem, different altitude

The comparison people actually search for is Mio against Tokio, and the answer is that they are not competitors. Tokio is a runtime: it gives you async/await syntax, a work-stealing scheduler, timers, channels, and a task abstraction. Mio is the readiness layer underneath. Tokio's I/O driver registers sockets with Mio and translates readiness events into task wakeups.

The difference in approach shows up in what you write. With Tokio you write an async fn and await a read. With Mio you write a match on event.token() and call the socket yourself. Tokio hides the state machine; Mio makes you the state machine. That is a genuine trade-off in both directions. Tokio costs you a dependency tree, a scheduler you did not choose, and a runtime you must initialize. Mio costs you everything Tokio does for you.

There is a middle option worth naming: you can use Tokio for most of an application and drop to Mio for a socket that needs unusual registration semantics, since Tokio exposes its Mio handle through its own API. That is a narrower use than replacing Tokio outright.

One practical note on the Tokio side: the search data around this project includes people looking for how to add Tokio with all features. That is a Tokio question, not a Mio one, and the answer lives in Tokio's own documentation.

Platforms, MSRV and the upgrade bill

Mio supports a wide set of targets: Android (API level 21), DragonFly BSD, FreeBSD, Linux, NetBSD, OpenBSD, WASI, Windows, Wine, iOS, macOS, and Solaris. On Windows it does not use select or plain overlapped I/O; the README states that polling is implemented with the wepoll strategy, which reaches the Windows AFD system for socket readiness. That is a meaningful implementation detail if you are debugging Windows-specific behaviour, because the code path is not the one most Windows networking tutorials describe.

The MSRV policy is the part that affects your upgrade planning. The minimum Rust version is fixed within a minor series but can rise when the minor version rises. The README lists v0.8 at Rust 1.46, v1.0 at Rust 1.70, v1.1 at Rust 1.71, and v1.2 at Rust 1.71. Cargo.toml for the current release sets rust-version = "1.71" and edition = "2021". If your toolchain is pinned below 1.71, you stay on an older minor, and the README warns that dependencies may follow different MSRV rules, so the guarantee is not absolute.

On licensing: the crate is MIT, declared both in Cargo.toml and in the README badge, with the LICENSE file at the repository root. MIT is permissive and imposes no source-disclosure obligation on your own code. This is a description of what the repository states, not legal advice; if your organisation has a policy on dependency licences, run it through that process.

Maintenance is visible in the repository rather than in release tags. The most recent release listed is v0.8.0 from 2021-11-13, yet Cargo.toml carries version 1.2.3 and the last push to the default branch was on 2026-09-02. The release list and the crate version disagree, which is worth knowing before you assume the tags tell you what is current.

Editorial conclusion

Adopt Mio when you are writing a runtime, a protocol implementation, or a single-threaded event loop where you want to control every syscall and allocation. Do not adopt it if you want async/await, timers, or a thread pool: the README lists those as non-goals, and Tokio is the project's own recommendation for an easier start. Before committing, verify the MSRV for the minor version you pin (v1.2 requires Rust 1.71), confirm the feature flags you need are enabled, and check that your target platform appears in the supported list.

Frequently asked questions

Is Mio a replacement for Tokio?

No. Mio is a low-level readiness notification library, and the README points readers who want something easier to get started with toward Tokio. Tokio builds on Mio rather than competing with it.

Which features do I need to enable to use Mio's TCP types?

The README states that features = ["os-poll", "net"] must be specified for its TcpListener and TcpStream example. The default feature set only enables log.

What is the minimum Rust version for Mio 1.2?

The README lists v1.2 at Rust 1.71, and Cargo.toml sets rust-version = "1.71". The MSRV is fixed within a minor series but can increase when the minor version increases.

Does Mio handle timers and thread pools?

No. The README's non-goals section explicitly omits file operations, thread pools and multi-threaded event loops, and timers. Those are left to the user or to higher-level libraries.

How does Mio poll sockets on Windows?

The README states that the Windows implementation uses the wepoll strategy, which accesses the Windows AFD system for socket readiness events. It is not a select-based loop.

Official sources

  1. Issues
  2. License: MIT
  3. README
  4. Releases
  5. tokio-rs/mio on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/tokio-rs-mio.svg)](https://hysenlabs.com/projects/tokio-rs-mio)