Dev.to · 7 min read

Zero Dependencies, 456 Tests, and One Bug All of Them Missed

Zero Dependencies, 456 Tests, and One Bug All of Them Missed

The test suite was green. All of it. Every assertion about the QR encoder I'd written passed, the way it had passed for weeks. I held my phone up to the screen anyway, more out of habit than suspicion, and the camera just sat there. No beep, no flash, no redirect. Nothing. A perfectly scannable-looking grid of black and white squares that no scanner on Earth could actually read. That contradiction, a fully passing suite sitting next to a completely broken feature, is the whole story of this project. I want to start there instead of at the beginning. The premise The project is darkroom: point it at a folder of photos, and it serves a browsable timeline to your phone over your own Wi-Fi, sorted by the date each photo was actually taken, with duplicate detection that catches the same shot at a different size or a different crop. Nothing novel about the pitch. The constraint is what made it interesting: Cargo.toml's [dependencies] section is empty. Not "minimal." Empty. cargo tree prints one node: darkroom itself. In most languages this is a non-event. Python has Pillow. Node has sharp. Go's standard library ships image/jpeg and compress/flate for free. Rust's standard library ships none of it: not a JPEG decoder, not a compressor, not a QR generator, not even an HTTP server past raw TcpListener. Choosing Rust for this project wasn't a language preference. It was choosing which gaps I wanted to personally fill in. What got reimplemented Fourteen substitutions ended up in the final count, each one something I'd have pulled from crates.io in an afternoon on any other project. Two are worth walking through, because the numbers turned out to matter more than the code. The JPEG decoder is the one I was proudest of and least sure about. Marker parsing, Huffman table construction, the inverse DCT, chroma upsampling, baseline and progressive both. None of it is exotic, all of it is tedious, and none of it tells you whether it's right just by compiling. So I diffed my decoder's output against jpeg-js, pixel by pixel, across eleven corpus images. Mean absolute difference: 0.35 to 0.54 per channel. Max difference anywhere, across 3.7 million samples: 3, out of 255. That's not "looks right." That's a number I can defend. DEFLATE was the one that actually hurt, because unlike JPEG it isn't decorative: PNG can't write a single pixel without RFC 1951 compression underneath it, and Rust's standard library has none. I wrote LZ77 with a hash-chain match finder, static and dynamic Huffman, and the inflate side to match, then checked every stream shape I could think of (empty, short, repetitive, prose, incompressible, every byte value) against real gunzip 1.13, including gunzip -t. All of them round-tripped clean. A 9-bit direct lookup table for short Huffman codes later made inflate 8.7 times faster on a 192 MB stream: fifteen seconds down to under two. What the standard library made painful std::net::TcpListener and blocking TcpStream, and nothing else. No HTTP, no async runtime, no concept of a request. SystemTime gives you seconds since the epoch and stops there: no dates, no calendar, no formatting. There's no RNG at all, though RandomState turns out to be quietly seeded from OS entropy if you go looking for it. No interface enumeration, so darkroom finds its own LAN address by opening a UDP socket, connect()-ing it to a routable address without ever sending a packet, and reading back which interface the kernel picked. That one actually paid off: my dev machine has seven non-loopback interfaces, six of them link-local junk, and a naive "first non-loopback" scan would have printed a QR code pointing nowhere. None of these gaps are surprising in isolation. What's uncomfortable is realizing, one substitution at a time, that "the standard library" and "a standard library" are very different phrases, and Rust's is closer to a systems toolkit than a batteries-included one. The package that stopped looking necessary tokio + hyper + axum: three of the largest dependency trees in the Rust ecosystem, and the default answer to "how do I serve HTTP" for almost everyone. darkroom serves a photo timeline to one or two phones on a home network. It doesn't need an async runtime scheduling thousands of concurrent connections; it needs a socket, a thread per connection, and correct handling of keep-alive, chunked transfer, range requests, and conditional GET with ETags, all of which fit in a few hundred lines on top of raw TcpStream. Watching an async stack with three of the biggest dependency trees in the ecosystem behind it shrink down to "a thread pool and a loop" was the moment zero dependencies stopped feeling like a constraint and started feeling like the correct scope for the problem. The edge case that ate an afternoon (and then some) Back to the QR code. The encoder implements the actual spec: GF(256) arithmetic, Reed-Solomon generator polynomials, all eight mask patterns with real penalty scoring, format and version information. Every unit test for it passed from day one, because every test checked the encoder's output against the encoder's own understanding of the format. That's the failure mode nobody warns you about: a self-consistent bug is invisible to a self-consistent test suite. It took two decoders that had never seen my code, OpenCV and jsQR, plus a byte-level trace of the actual bits being written, to find it. // what write_format() did: self-consistent, and wrong let bit = |i: u32| (format >> i) & 1 == 1; // what ISO/IEC 18004 actually requires: MSB first let bit = |i: u32| (format >> (14 - i)) & 1 == 1; One reversed bit-ordering formula. The tests had been written against the same reversed convention, so flipping the writer without also flipping the test would have just moved the bug, not fixed it. That's exactly why an outside decoder was non-negotiable, not a nice-to-have. I fixed the one line, re-ran the encoder, opened the camera on my own phone, and scanned it. It worked. That was the first time in the project I trusted the QR code, and it was also the first time I'd actually tested it against anything other than my own assumptions. The honest lesson isn't "test more." I had tests. The lesson is that a test suite can only prove your code agrees with itself. Proving it agrees with reality requires a witness that never read your source. What I refused to build HEIC files, the default format on modern iPhones, catalogue correctly in darkroom: date, camera, GPS, all read fine, because the container is plain ISOBMFF. The pixels inside are HEVC-encoded, and decoding that means CABAC arithmetic coding, quadtree CTU partitioning, thirty-five intra-prediction modes, deblocking, and SAO: several thousand lines of bit-exact video codec work that fails silently when it's subtly wrong. I didn't attempt it, and I didn't paper over the gap either. Those files show an explicit "preview unavailable" placeholder instead of a fake thumbnail. Same story for TLS: it isn't in Rust's standard library, full stop, so darkroom is plain HTTP and LAN-only by construction, and the README says so under a section titled "Limits," not buried in a footnote. By the numbers Zero runtime dependencies. 456 tests, zero warnings. About 13,500 lines of Rust. Fourteen substitutions, each checked against something that wasn't my own code: gunzip 1.13 for compression, Pillow for PNG and GIF and EXIF, jpeg-js for pixel-level JPEG accuracy, all 176 files of the PngSuite conformance set, and OpenCV plus jsQR for the bug that started this whole writeup. If there's one thing worth taking away from a project built entirely out of things a language's standard library refused to give me, it's this: removing your dependencies doesn't remove your risk, it just moves the risk into code you now have to go find an outside witness to check. The green checkmark was never the hard part. Finding a witness that had never read my code, and getting it to disagree with me, that was. Checkout my repo darkroom: github.com/Abhishekjha18/darkroom Demo Video : https://youtu.be/kSMSNlAQ4wQ?si=vKUv4IXTnHIxlD3f

This is a summary aggregated from Dev.to. Read the complete article on the original site:

Read full article at Dev.to

More Programming & Dev News