epoll
A minimal, safe Rust wrapper around the Linux kernel's epoll API for event-driven I/O.
Repository Health
Technical Analysis
epoll is a small Rust crate that provides a safe wrapper around the Linux kernel’s epoll(7) interface — the syscall family (epoll_create1, epoll_ctl, epoll_wait) that underlies scalable, non-blocking event notification on Linux. Instead of calling into libc directly and managing raw file descriptors and unsafe casts by hand, epoll exposes typed functions (create, ctl, wait, close) plus a bitflags-based Events type covering the full set of epoll event flags (EPOLLIN, EPOLLOUT, EPOLLET, EPOLLONESHOT, EPOLLEXCLUSIVE, and more).
The crate is intentionally tiny — a single ~190-line src/lib.rs with two dependencies, libc and bitflags — making it a low-level building block rather than a full async runtime. It has shipped multiple major versions since 2015 and remains a dependency of higher-level async I/O and networking crates that need direct epoll access without re-deriving the unsafe FFI boilerplate themselves.
What You Get
- Safe
create(),ctl(),wait(), andclose()functions covering the full epoll_create1/epoll_ctl/epoll_wait/close syscall lifecycle - A
ControlOptionsenum for EPOLL_CTL_ADD/MOD/DEL operations, avoiding raw integer constants at call sites - An
Eventsbitflags type covering EPOLLIN, EPOLLOUT, EPOLLET, EPOLLERR, EPOLLHUP, EPOLLRDHUP, EPOLLPRI, EPOLLWAKEUP, EPOLLONESHOT, and EPOLLEXCLUSIVE - A
#[repr(C)]Eventstruct that maps directly ontolibc::epoll_eventfor zero-cost interop with the kernel ABI - Automatic conversion of negative syscall return codes into
io::Error::last_os_error()results
Common Use Cases
- Implementing a custom reactor or event loop for an async runtime without depending on a larger I/O framework
- Building a lightweight TCP/UDP server that needs direct, edge-triggered epoll control for performance tuning
- Prototyping or teaching how Linux epoll-based event notification works with a minimal, readable Rust surface
- Adding epoll-based file descriptor monitoring (pipes, eventfd, signalfd, timerfd) to a systems-level Rust tool
Under The Hood
Architecture: The crate is a single flat module (src/lib.rs, ~190 lines) with no internal layering: four public functions (create, ctl, wait, close) call directly into libc’s epoll_create1, epoll_ctl, epoll_wait, and close, each wrapped in an unsafe block and piped through a shared cvt() helper that turns a negative libc::c_int return into an io::Error via Error::last_os_error(). Data flow is a thin pass-through — callers build an Event (a #[repr(C)] struct holding a u32 events mask and a u64 data payload, packed on x86_64 to match the kernel’s epoll_event layout exactly), pass it to ctl() by reinterpret-casting a &mut Event to mut libc::epoll_event, and read populated Event values back out of a caller-supplied &mut [Event] buffer from wait(). There is no reactor, no event loop, and no ownership of the epoll file descriptor itself — RawFd values are opaque i32s the caller is responsible for lifecycle-managing. Tech Stack: The crate targets Rust 2018-era edition conventions (explicit extern crate declarations for libc and the #[macro_use] bitflags import) and depends on exactly two crates: libc (^0.2) for the raw epoll_create1/epoll_ctl/epoll_wait/close FFI bindings and platform constants (EPOLL_CTL_ADD, EPOLLIN, etc.), and bitflags (^2) to generate the Events bitflags struct with Debug/Clone/Copy/PartialEq/Eq/PartialOrd/Ord/Hash derives. No build script, no platform-conditional compilation beyond the implicit Linux-only assumption (libc::epoll_ symbols simply don’t exist on non-Linux targets), and no async runtime dependency of any kind — this is meant to be a building block underneath one, not a runtime itself. Code Quality: There are no automated tests in the repository (no tests/ directory, no #[cfg(test)] module in lib.rs) and no CI configuration beyond a stale Travis badge in the README, so correctness rests on the crate’s small surface area and the ABI-compatibility guarantee of the #[repr(C)] Event struct rather than on verified behavior. Error handling is consistent and idiomatic — every syscall wrapper returns io::Result<T> via the single cvt() helper rather than mixing panic and Result styles. Naming closely mirrors the underlying C API (create, ctl, wait, close map 1:1 to epoll_create1/epoll_ctl/epoll_wait/close), which keeps the crate easy to cross-reference against the epoll(7) man page but offers little Rust-idiomatic abstraction (e.g., no RAII guard that auto-closes the epoll fd on drop). API Design: The four-function surface (create/ctl/wait/close) plus two supporting types (ControlOptions, Events) is small enough to learn from the README and man page alone, and the direct 1:1 naming against the C API minimizes translation overhead for anyone already familiar with epoll. The trade-off is that all fd lifecycle management, safety invariants (e.g., not using a closed fd), and reactor-level bookkeeping are left entirely to the caller — there’s no Drop impl, no builder, and no higher-level convenience layer, so integrating it requires writing that scaffolding yourself, which is expected for a crate positioned as a thin syscall wrapper rather than a framework.