Nov 07, 2025 · 1 minute read
In the previous blog post I showed how to use getaddrinfo_async_start from C. However I didn’t stop there and decided to see if I can fit that into the smol async stack in Rust.
smol is a small and fast async runtime. It’s an alternative to the probably more known tokio project and is based on several smaller crates like async-io and async-net as its building blocks.
To make use of getaddrinfo_async_start I had to start at the lowest layer: rustix, the safe Rust bindings to syscalls. It didn’t know about EVFILT_MACHPORT for [kqueue…
Nov 07, 2025 · 1 minute read
In the previous blog post I showed how to use getaddrinfo_async_start from C. However I didn’t stop there and decided to see if I can fit that into the smol async stack in Rust.
smol is a small and fast async runtime. It’s an alternative to the probably more known tokio project and is based on several smaller crates like async-io and async-net as its building blocks.
To make use of getaddrinfo_async_start I had to start at the lowest layer: rustix, the safe Rust bindings to syscalls. It didn’t know about EVFILT_MACHPORT for kqueue and now it does. One layer above we have polling, an interface for all things ... to poll, specifically kqueue on macOS. In there we forward the mach port knowledge down the stack. One further up async-io implements IO on top of the polling mechanism in use. We register a mach port there to get informed when it’s ready. Then in the second to last layer of this smol stack we implement DNS resolving on top of getaddrinfo_async_start in async-net. A third of that code is reimplementations of the C structs in use, another third is code copied from Rust’s libstd. Just the last third is code to integrate it with smol. smol itself doesn’t require any changes—it’s what plugs all the other crates together. The complete changes across those crates are available in this patch set.
With all of that applied this works:
smol::block_on(async {
let addrs = smol::net::resolve("fnordig.de:443").await.unwrap();
println!("{arg}:");
for a in addrs {
println!("\t{a}");
}
})
Yes, that worked before, but it put DNS resolving onto another thread. Now it doesn’t do that anymore. And it resolves:
fnordig.de:443:
[2a01:4f8:221:2114::7]:443
46.4.212.174:443
This is nowhere near production ready—remember it’s a secret API and you probably shouldn’t use it.