DEV Community

Cover image for I Built a Database in Rust With Zero Dependencies (and What the Standard Library Quietly Gave Me)
Sanjay Kumar Sah
Sanjay Kumar Sah Subscriber

Posted on

I Built a Database in Rust With Zero Dependencies (and What the Standard Library Quietly Gave Me)

TL;DR — I built a small database in Rust for a 72-hour hackathon with one rule: no third-party packages allowed. Just the language and nothing else. Here's what I had to write by hand, in plain English, and the surprisingly capable standard-library features that made it possible. Full source and a 5-minute video at the bottom.


First, what's a "dependency" — and why does anyone care?

When you build software today, you almost never write everything yourself. You install code other people wrote. In the JavaScript world you run npm install, in Python it's pip install, in Rust it's adding a "crate." These installed packages are called dependencies.

They're incredibly useful. They're also a little scary when you stop and look:

  • A typical modern web app pulls in over 1,200 packages once you count everything. 1
  • In 2025 alone, public registries catalogued 454,600 new malicious packages — pushing the running total past 1.2 million. 2
  • AI coding assistants now invent package names that don't exist — one study across 576,000 samples found 19.7% of AI-suggested packages were hallucinated — and attackers register those fake names and wait for someone to install them. 3

The famous example: back in 2016 a developer deleted an 11-line package called left-pad (it just added spaces to the start of a string) and it broke builds across half the internet — React, Babel, thousands of projects. Eleven lines. A stranger's eleven lines, sitting inside everyone's app. 4

So a hackathon called Zero Dependency a hackathon run by @partnerships_raptors asked a simple, slightly rebellious question:

What if you built something genuinely useful using **nothing but the language itself?

No packages. No npm install. Just the "standard library" — the batteries that ship inside the language when you download it. I said yes. Here's what happened.


What I built: zdb, a tiny database

I built zdb, a key-value store. If you've used a dictionary in Python or an object in JavaScript, you already understand it: you put a value under a key, and later you get it back.

zdb put greeting "hello, world"
zdb get greeting
# → hello, world
Enter fullscreen mode Exit fullscreen mode

The twist: it saves everything to disk and survives crashes. Close the program, reopen it, your data's still there. Pull the power cord mid-write, and it recovers cleanly instead of corrupting.

That's the kind of thing people normally reach for a big library like RocksDB, sled, or SQLite to do. I wrote the whole engine by hand, in one Rust file, with an empty dependency list. Here's the proof, and it's the whole point:

$ cargo tree
zdb v0.1.0
Enter fullscreen mode Exit fullscreen mode

That's it. That's the entire "supply chain." One line: my own code.


The fun part: what I'd normally install, and what replaced it

This is the heart of the story. For each thing I needed, I'll show you the package I'd usually grab — and the standard-library feature I used instead. No jargon, I promise.

1. Turning data into bytes → I did it by hand

Normally I'd install: serde + bincode (Rust's go-to tools for converting data structures into a stream of bytes you can save to a file).

What it actually takes: A file is just a long line of numbered boxes, each holding one byte. To save a record, I decided exactly which byte goes where:

[ checksum ][ key length ][ value length ][ the key ][ the value ]
Enter fullscreen mode Exit fullscreen mode

That's the entire "format." Rust's standard library already knows how to turn a number into bytes (to_le_bytes()) and back (from_le_bytes()). Once I picked the layout, saving and loading was a dozen lines. No library needed — I just had to decide the shape instead of letting a package decide it for me.

The lesson: serialization libraries are amazing when your data is complicated. When your data is simple and you control both ends, hand-writing the format is clearer and faster.

2. Detecting corruption → a 15-line checksum

Normally I'd install: crc32fast (checks whether data got scrambled).

What it actually takes: A "checksum" is a small number calculated from your data. Save it alongside the data; if you recompute it later and it doesn't match, you know something got corrupted. The specific recipe I used — CRC-32 — is the same one inside ZIP files and your ethernet cable.

It's a small loop over a lookup table. The magic moment: I tested my version against the internationally-known "correct answer" (CRC32("123456789") must equal 0xCBF43926) — and it matched on the first try. My hand-written 15 lines produce bit-for-bit the same result as the popular package.

The lesson: Some famous algorithms look intimidating because they have scary names, but the actual code is short and testable.

3. Stopping two programs from clobbering the same file → a "lock file"

Normally I'd install: fs2 (file locking).

What it actually takes: I needed to make sure two copies of zdb don't write to the same database at once and scramble it. The trick is beautifully old-school: try to create a file called LOCK, using a mode that says "only succeed if this file does not already exist." If it's already there, someone else is using the database, so I refuse to start.

The standard library has exactly this: OpenOptions::new().create_new(true). One method call. When the program exits cleanly, it deletes the lock. It's the digital version of hanging a "occupied" sign on a door.

The lesson: The standard library often hides a whole feature inside a single well-named option.

4. Reading command-line options → a simple loop

Normally I'd install: clap (parses --flags and sub-commands for command-line tools).

What it actually takes: For a handful of commands (put, get, del, list), I just read the words the user typed and matched on them. Rust hands you those words with std::env::args(). A match statement does the rest. Big argument-parsing libraries are worth it for huge tools with 50 options — for 6 commands, a loop reads more clearly than a library's special syntax.

5. A "run this once, lazily" global → now built in

Normally I'd install: once_cell or lazy_static (for setting up a value the first time it's used).

What it actually takes: Nothing new — Rust added this to the standard library in version 1.80 as LazyLock. This is the happiest kind of "package killer": the language caught up, and a crate millions of people still install out of habit is now unnecessary. I used it to build my checksum table exactly once.


The thing that turned out harder than the docs made it look

Here's the honest part — hackathons reward honesty over hype.

One of the bonus challenges was a "reproducible build": compile the program twice and get a byte-for-byte identical file both times. Sounds trivial, right? Same code, same result?

Nope. My first two builds were different — 20 bytes apart. I nearly assumed my code was non-deterministic.

After comparing the two files byte by byte, the culprit was almost comedic: on Windows, the compiler stamps the current timestamp and a random ID into the program file's header. Nothing to do with my code — the build process itself was sprinkling in randomness.

The fix was a single linker flag (/Brepro, which tells the toolchain "zero out the timestamp, make the ID a hash of the content instead"). 5 After that, both builds produced the identical hash:

build A: 6d93cd01…dc0f
build B: 6d93cd01…dc0f   ✅ identical
Enter fullscreen mode Exit fullscreen mode

The lesson: "Deterministic" is a discipline, not a default. Most of us never notice because we never check. Checking is the whole exercise.


So… should you delete all your dependencies?

No! That's not the takeaway, and the hackathon organizers said so themselves. Libraries exist for good reasons. You should not hand-roll cryptography, and you should not rewrite a mature database for production on a Tuesday.

The real lesson is smaller and more useful:

Know what's underneath the packages you install. A surprising number of them wrap a feature your language already has for free.

Before your next npm install / pip install / cargo add, spend 30 seconds asking: "Does the standard library already do this?" Often — especially in modern Node, Python, Go, and Rust — the answer is yes. Fewer strangers in your code. Less to audit. One less name for an attacker to hijack.

That's the whole idea. And honestly? Building the "boring" layer by hand was the most fun I've had coding in months.


See it in action (5-minute video)

I recorded a short walkthrough — the database working, the crash-recovery test, and the cargo tree moment that proves the dependency list is empty:

The code (MIT licensed, read it top to bottom)

The whole engine and command-line tool live in one readable file, src/main.rs. If you've ever been curious what's inside a database, it's a friendly place to start.


Sources


Built for the Zero Dependency Hackathon 2026 by Hackathon Raptors. All statistics above are cited in the Sources section. If this was useful, a ❤️ or a ⭐ on the repo genuinely helps. Thanks for reading!


  1. OneUptime, "The hidden costs of dependency bloat in software development" (1,200+ full-tree dependencies). https://oneuptime.com/blog/post/2025-09-02-the-hidden-costs-of-dependency-bloat-in-software-development/view 

  2. Sonatype, "2026 State of the Software Supply Chain" (454,600 new malicious packages in 2025; 1.2M cumulative). https://www.sonatype.com/state-of-the-software-supply-chain/2026/open-source-malware 

  3. Spracklen et al., "Package Hallucinations" (USENIX Security 2025) — 19.7% of AI-suggested packages hallucinated across 576k samples; see also "Slopsquatting". https://en.wikipedia.org/wiki/Slopsquatting 

  4. The Register, "How one dev broke Node, Babel and thousands of projects in 11 lines of JavaScript" (2016). https://www.theregister.com/2016/03/23/npm_left_pad_chaos/ 

  5. Microsoft, link.exe /Brepro (reproducible builds). https://learn.microsoft.com/en-us/cpp/build/reference/link-command-file 

Top comments (0)