Building in public sounds noble until you're three days into a socket leak that only manifests under 10k concurrent connections, your CI is red, and someone on Reddit asked if your crate is 'production-ready'.
This isn't a tutorial. It's a postmortem of what actually happens when you ship Rust code that other humans depend on.
I wrote a DNS resolver in Rust because trust-dns felt heavy. Three months later, a user reported that their proxy was leaking file descriptors. I ran lsof, saw thousands of open sockets, and immediately assumed the worst: a leak in my async runtime integration.
I spent two days instrumenting every .await point, wrapping every TcpStream in a custom guard, and writing a fuzz harness that opened 50k connections just to watch the leak reproduce in under a second.
A transitive dependency of ring, which trust-dns used for DNS-over-HTTPS, was holding onto entropy pools differently. My replacement library—my own code—wasn't the issue. The issue was that I'd replaced one dependency with another and forgotten to audit the entire tree.
I ran cargo tree -e features and found 87 crates in my dependency graph. Eighty-seven. For a DNS resolver.
Half of them were pulled in by tokio-postgres, which I'd added to log resolution metrics. Half of those were pulled in by serdejson, which I used to serialize a struct I never actually sent anywhere.
I deleted tokio-postgres. I replaced serdejson with a hand-rolled fmt::Write serializer that was 40 lines long. I removed three logging macros I'd added 'for debugging' and never removed.
My binary went from 4.2 MB to 1.8 MB. My build time dropped from 92 seconds to 37.
I'd implemented IPv4. I'd tested IPv4. I'd never thought about IPv6 because my test environment didn't use it.
I spent a week adding IPv6 support, only to discover that the socket2 crate's IPv6 API was subtly different from what I expected, and the standard library's Ipv6Addr parsing didn't handle zone IDs the way my resolver needed.
I wrote tests for IPv6. I wrote tests for zone IDs. I wrote tests for dual-stack fallback.
Building in public means every bug is a public failure. Every refactor is a question about your competence. Every release is scrutinized by people who have never opened a PR but will tell you exactly how you should have done it.
It also means help. Real help. A user submitted a PR fixing a panic in my error path. Another wrote a fuzz target that found a stack overflow in my recursive parser. A third pointed me to a CVE in a dependency I'd missed.
Rust doesn't make you write correct code. It makes you write compilable code. The borrow checker doesn't care if your logic is wrong—it just makes sure you don't access memory you shouldn't.
I spent a day debugging a panic that turned out to be an integer overflow in my packet length calculation. Rust's default release profile panics on overflow. My test suite ran in debug mode, where overflow wraps. It took a production crash to find it.
I added #![deny(arithmeticsideeffects)] and a CI job that ran tests in release mode. It's the closest thing to a safety net I have.
I still use my own DNS resolver. It's faster than trust-dns for my use case. It's smaller. It doesn't pull in tokio-postgres.
