Most “modern” async solutions these days come bundled with thread pools, executors, reactors, hundreds of macros, and half a megabyte of generated state machines just to read 4kb from a socket.
I’m trying to keep a personal networking tool extremely lean. Think single binary < 5mb, no heavy runtime if possible. But I still want proper non-blocking I/O without busy-looping or blocking the whole program.
Heres the kind of "ugly-but-works" code I usually end up writing when I want to stay close to the metal:
Rustuse std::io::{self, Read, Write};
use std::net::TcpStream;
use std::os::unix::io::AsRawFd;
fn main() -> io::Result<()> {
let mut stream = TcpStream::connect("1.1.1.1:443")?;
stream.set_nonblocking(true)?;
let mut buf = vec![0u8; 4096];
loop {
match stream.read(&mut buf) {
Ok(0) => break, //eof
Ok(n) => { /* handle data */ }
Err(e) if e.kind() == io::ErrorKind::WouldBlock => {
//this is the part that usually turns into mio / epoll / kqueue / iocp / select spaghetti
}
Err(e) => return Err(e),
}
}
Ok(())
}
The question is: how do you people actually handle the “wait for something to happen” part in 2026 without immediately reaching for tokio/async-std/libuv/go runtime/whatever-the-800-line-crate-of-the-week-is?