epoll

A minimal, safe Rust wrapper around the Linux kernel's epoll API for event-driven I/O.

Library
Cargo
v4.4.0
42stars
Mozilla Public License 2.0

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
29/100Needs Attention
Development Activity0
Maintenance20
Community24
Maturity60
Momentum12

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
59/100Fair
Architecture60
Code Quality55
Innovation50
Learning Curve70

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(), and close() functions covering the full epoll_create1/epoll_ctl/epoll_wait/close syscall lifecycle
  • A ControlOptions enum for EPOLL_CTL_ADD/MOD/DEL operations, avoiding raw integer constants at call sites
  • An Events bitflags type covering EPOLLIN, EPOLLOUT, EPOLLET, EPOLLERR, EPOLLHUP, EPOLLRDHUP, EPOLLPRI, EPOLLWAKEUP, EPOLLONESHOT, and EPOLLEXCLUSIVE
  • A #[repr(C)] Event struct that maps directly onto libc::epoll_event for 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.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search