Mio hands you a poll loop and refuses to add files, threads, or timers
Metal I/O library for Rust.
At a glance
- What is it?
- A low-level Rust crate for non-blocking I/O and readiness notification over epoll, kqueue, and IOCP, where the interesting decisions are the feature flags you must enable by hand, the three capabilities it declares as non-goals, and a minimum Rust version that moves with each minor release.
- Who is it for?
- Mio is the right dependency if you are writing your own event loop and want readiness notification with no runtime underneath you, and the wrong one if you need files, threads, or timers in the same abstraction. Before adopting it, check the MSRV line against your toolchain, because 1.2 needs Rust 1.71 while 1.0 needed 1.70 and 0.8 needed 1.46, and read the feature list, since the default set builds you a shell without Poll or mio::net.
- 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 1 day 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 November 13, 2026, and from our analysis. They are not legal advice.
Star history
- tokio-rs/mio7,140 ★ · +40
| Repository | 2026-09-23 | 2026-11-13 |
|---|---|---|
| tokio-rs/mio | 7.1k | 7.1k |
40 Stars in the last 51 days, measured from the daily GitHub snapshot. Compare with other projects
Editorial analysis
The default feature set is a shell, and the example needs two flags you must add
Adding the crate is the easy part:
[dependencies]
mio = "1"What that line does not tell you is that the default set is `default = ["log"]`, and Cargo.toml says plainly that by default Mio only provides a shell implementation. The types you actually need are behind their own flags: `os-poll` enables the `Poll` and `Registry` types, and `net` enables the `mio::net` module with the networking primitives. The README states the requirement for its own example, that `features = ["os-poll", "net"]` must be specified, which tells you the failure mode of skipping them, a compile error rather than a runtime surprise. There is a third flag, `os-ext`, which turns on `os-poll` plus additional OS specific extensions such as the Unix `pipe(2)` path, and it also pulls in two windows-sys features, `Win32_System_Pipes` and `Win32_Security`. So the crate you depend on changes shape depending on which of three flags you check, and the documentation for all of them lives in the `mio::features` module rather than in the README.
The quick start snippet stops at 'Start listening for incoming'
The Rust example in the README is cut off mid-comment, which is worth knowing before you copy it:
use std::error::Error;
use mio::net::{TcpListener, TcpStream};
use mio::{Events, Interest, Poll, Token};
// Some tokens to allow us to identify which event is for which socket.
const SERVER: Token = Token(0);
const CLIENT: Token = Token(1);
fn main() -> Result<(), Box<dyn Error>> {
// Create a poll instance.
let mut poll = Poll::new()?;
// Create storage for events.
let mut events = Events::with_capacity(128);
// Setup the server socket.
let addr = "127.0.0.1:13265".parse()?;
let mut server = TcpListener::bind(addr)?;
// Start listening for incomingWhat you get is a poll instance, an event buffer with a capacity of 128, and a bound listener, then nothing. The registration call that connects the socket to the poll instance, the accept, the read and write calls, and the loop that waits on events are all yours to write. The repository ships three runnable files under examples/ that cover that ground: tcp_server.rs, tcp_listenfd_server.rs, and udp_server.rs, and the API docs are linked for both the v1 line and the older v0.8 line. Read one of those examples before planning a loop, because the README gives you the setup and stops.
File operations, thread pools, and timers are named as non-goals
Mio draws a hard line and puts it in writing, under a heading called Non-goals: file operations, thread pools and a multi-threaded event loop, and timers. The reason given is that these are left to the user or to higher-level libraries. This is the single most useful thing to know before you start, because each omission is a thing you now have to source. Read regular files and you need the standard library or a filesystem crate, since nothing in Mio's feature list mentions files. Run more than one core and you write the dispatch yourself, because the README names thread pools and a multi-threaded event loop as non-goals. Implement a timeout and you build it on something outside the crate, because there is no timer type in the API. The README also points you at Tokio if you want something easier to get started with, which is the maintainers own framing of where this crate sits in a stack.
Three backends, and Windows sockets go through wepoll and AFD
The event queue is backed by epoll on Linux, kqueue on the BSDs and macOS, and IOCP on Windows, and Mio picks per platform without asking. The Windows path is the one with a named strategy: socket polling uses wepoll, which reaches socket readiness events through the Windows AFD system, and the Cargo.toml explains the cost of that choice, since the windows-sys dependency carries Wdk_Foundation, Wdk_Storage_FileSystem, and Wdk_System_IO with the comment that each is required for AFD, alongside Win32_Networking_WinSock, Win32_Storage_FileSystem, Win32_Foundation, and Win32_Security. The advertised feature set also includes non-blocking TCP, UDP, and UDS, zero allocations at runtime, and platform specific extensions. The supported platform list is wide, covering Android at API level 21, DragonFly BSD, FreeBSD, Linux, NetBSD, OpenBSD, WASI, Windows, Wine, iOS, macOS, and Solaris, and Mio generally supports the same platform versions as rustc does unless noted otherwise.
The MSRV floor is fixed per minor version and dependencies may not follow it
The policy is written out rather than left implicit: the MSRV is fixed for a given minor 1.x version, and it can be raised when the minor version is bumped, going from 1.0 to 1.1 is allowed to raise it, and users who cannot raise their Rust version are expected to pin an older minor instead. The table gives the numbers, v0.8 needing Rust 1.46, v1.0 needing 1.70, v1.1 and v1.2 both needing 1.71, which matches `rust-version = "1.71"` in Cargo.toml alongside `edition = "2021"` and `version = "1.2.4"`. The caveat is the sentence right after the table: Mio has dependencies that might have different MSRV policies, the maintainers try to stick to the stated policy when updating dependencies, and this is not always possible. For a reader that means your effective floor is the maximum across the tree, not the number in the table, so a dependency bump can raise what you need to install before Mio itself does.
Two cfg flags force a slower backend and are explicitly unsupported
When the platform default is not the one you want, two configuration flags exist, and the README is blunt about both. `mio_unsupported_force_poll_poll` switches `mio::Poll` to an implementation based on `poll(2)`, and `mio_unsupported_force_waker_pipe` switches `mio::Waker` to one based on `pipe(2)`. The context is that Mio uses the best implementation available per platform, where best usually means most efficient for its own use case, which is exactly why the implementation is often specific to a limited number of platforms and you may have several for the same functionality. The warnings matter for a reader: Mio does not officially support secondary implementations on platforms, and the flags may disappear in the future. Treat them as a way to bisect a platform problem on a local build, not as deployment configuration, because nothing promises they will still compile in a later 1.x.
The published crate ships src and examples, but not the tests directory
The `include` list in Cargo.toml decides what ends up on the registry, and it names Cargo.toml, LICENSE, README.md, CHANGELOG.md, and the Rust sources under src and examples. The tests/ directory is in the repository but not in the package, so anyone depending on Mio cannot read the test suite from the downloaded crate, only clone the repository for it. The Makefile reflects a thoroughness worth borrowing: `test` runs `cargo test --all-features`, while `test_all` runs `cargo hack test --feature-powerset` in both debug and release, and `check_all_targets` runs `cargo hack check` for each target in a list covering windows-msvc, freebsd, illumos, linux-gnu, linux-musl, netbsd, and redox. `test_sanitizer` requires a nightly compiler and an explicit `$SAN` value, and `dev` watches src/, tests/, the Makefile, and Cargo.toml with entr. On versioning, the manifest says 1.2.4 while the GitHub releases stop at the older 0.x line, v0.8.0 from 2021 being the newest of them, so check the v1 docs.
Editorial conclusion
Mio is the right dependency if you are writing your own event loop and want readiness notification with no runtime underneath you, and the wrong one if you need files, threads, or timers in the same abstraction. Before adopting it, check the MSRV line against your toolchain, because 1.2 needs Rust 1.71 while 1.0 needed 1.70 and 0.8 needed 1.46, and read the feature list, since the default set builds you a shell without Poll or mio::net. If you are porting from the 0.x line, use the v1 docs rather than the older tag, and treat the two cfg flags for forcing backends as debugging tools rather than configuration.
Frequently asked questions
When not to use Tokio?
Reach past Tokio and build on Mio directly when you want to own the event loop yourself. Mio names file operations, thread pools and a multi-threaded event loop, and timers as non-goals, leaving them to the user or to higher-level libraries, so anything that needs those belongs a layer up.
What is Tokio used for?
In this repository's framing, Tokio is the easier starting point for readers who do not want to write a low-level library themselves. Mio describes itself as focused on non-blocking APIs and event notification, and the README directs anyone looking for something easier to get started with to Tokio instead.
What are the benefits of using Tokio?
The repository makes no benefits case for Tokio itself; it makes one for the crate underneath. Mio lists non-blocking TCP, UDP and UDS, an I/O event queue backed by epoll, kqueue and IOCP, zero allocations at runtime, and platform specific extensions. The only mention of Tokio is as the easier path for newcomers.
What does Tokio stand for?
Nothing in this repository spells the name out. The README uses it only as a pointer, telling readers who want something easier than a low-level I/O library to see Tokio.
Official sources
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.
[](https://hysenlabs.com/projects/tokio-rs-mio)Star history chart for your README
[](https://hysenlabs.com/projects/tokio-rs-mio)Was this useful?