Open-source project
rosedblabs/rust-practice avatar
rosedblabs/rust-practice

Three tiny Rust projects that each explain one storage idea properly

some tiny learning projects in Rust

761 stars87 forksRustLicense varies

At a glance

What is it?
The rosedblabs/rust-practice repository holds three small Rust implementations: a Bitcask-style log-structured key-value engine, a multi-version concurrency control store, and an operator-precedence expression evaluator. Each is a few hundred lines and each README explains the mechanism rather than just listing features, which makes the repository usable as a reference for three quite different ideas.
Who is it for?
Adopt rust-practice if you want a readable reference implementation of a specific storage idea in Rust, since each project is small enough to read in an evening and the explanations cover the mechanism, the trade-off and the failure mode. Skip it if you need production-ready storage, because these are learning projects with no dependency policy, no release tags and an unstated licence.
Can I use it commercially?
Not without permission. GitHub finds no licence file in the repository, and without a licence all rights are reserved by default: you may read the code but not reuse it. Check the README, or ask the authors, before using it.
Is it still maintained?
Yes. The repository last received commits 59 days ago.
What is it written in?
Mainly Rust, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 25, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A repository of three unrelated things

The repository describes itself as some tiny learning projects in Rust, awesome, and the top level contains exactly three directories: mini-bitcask-rs, expr-eval and mvcc.

They are unrelated to each other, which is the point. A log-structured key-value store, a concurrency control mechanism and an expression parser have nothing to do with one another, and what they share is a size constraint and an intent. Each is a few hundred lines, and each is small enough that the whole implementation is a reasonable thing to hold in your head while reading it.

The scale is stated in the README. The mini-bitcask-rs entry describes it as a minimal Bitcask-model key-value disk storage engine, implementing the core log-structured set, get, delete, scan and merge logic in a few hundred lines. The expr-eval entry describes an arithmetic expression parser and evaluator built around operator precedence, in about 200 lines. The mvcc entry describes a minimal Multi-Version Concurrency Control implementation supporting transaction commit and rollback with set, get, delete and scan operations.

What makes the repository more useful than a code dump is that each of the three sections explains the underlying concept first and then says how the implementation here does it, in the simplest way. That structure means you can read the description of the model, and then the two or three sentences about the specific structure chosen, and you end up with both the idea and a concrete example of it.

The repository is Rust, the last push was on 2026-08-27, and the topics are bitcask and kv-store. There are no releases and no licence stated in the repository listing, so the terms are unclear and worth checking before you reuse any of the code.

Bitcask appends everything and keeps an index in memory

Bitcask is a log-structured key-value storage model, originally from Riak. Instead of updating data in place, every write, whether set or delete, is appended to the end of a log file. An in-memory hash index, called the keydir, maps each key to the offset and length of its most recent entry in the log. Old versions of a key just become dead space in the log until a merge, or compaction, rewrites only the live entries into a fresh file and reclaims disk space.

The stated trade-off is that this design trades some memory, one index entry per key, for very fast, mostly-sequential writes and O(1) reads.

The on-disk format is fixed-width header plus payload:

code
key_len(4B) | value_len_or_tombstone(4B) | key | value

A negative length marks a tombstone, which is how a delete is represented without removing anything from the log. That is the detail that makes the whole design work: a delete is an append, so the log is append-only and the index is the only thing that changes.

The implementation details are as direct as they can be. The keydir is a BTreeMap from a byte vector to an offset and length, and it is rebuilt in memory by scanning the log once on startup. Set and delete just append a new entry and update the keydir, and get looks up the keydir and then reads that one slice of the file.

Merge walks the current keydir, which means only the live keys, rewrites them into a new log file and swaps it in, dropping both tombstones and superseded versions. The memory cost of the index is the price, and the compaction is what keeps the disk from growing without bound.

MVCC uses version numbers and a set of uncommitted writers

Multi-Version Concurrency Control lets readers and writers work concurrently without blocking each other. Instead of overwriting a value, every write creates a new version tagged with a monotonically increasing version number. A transaction takes a version number when it begins and only ever sees versions that were already committed at that point, which is its snapshot, giving repeatable reads without taking read locks.

The implementation here keeps the state in two structures. A single global AtomicU64 counter hands out the next version number to each new transaction. Data is stored as a raw key and version to value in one BTreeMap, so all versions of a key sit next to each other in key order.

The visibility rule is the interesting part. A transaction remembers the set of transaction ids that were still active, meaning uncommitted, when it began. A version is visible to it only if that version's writer is not in the active set and its version number is not newer than the transaction's own.

The operations follow from that. Get scans a key's versions from newest to oldest and returns the first visible one. Commit just drops the transaction from the active set. Rollback deletes everything it wrote and drops it from the active set.

There is one more behaviour that deserves attention, because it is the difference between a teaching implementation and a naive one. A write checks the latest version of the key first, and if that version is not visible to the current transaction, which means another concurrent transaction touched it, it panics with a serialization error instead of silently overwriting.

So the implementation refuses to do a write-write conflict silently. The cost is that the error is a panic rather than a returned error, which is the simplest thing to do in a learning project and the first thing you would change in real code.

Each directory is a self-contained crate to read on its own

There is no framework around the three projects. The top level holds a .gitignore, the README and the three project directories, and there is no workspace file, no shared library and no build script tying them together.

That is a deliberate consequence of the intent. A learning project that shares code with its siblings is harder to read, because you end up tracing a helper into another crate. Here each directory stands alone, and the only thing the README does is explain the concept and point at the implementation.

The consequence for a reader is that you can pick one. If you are working on an embedded key-value store, mini-bitcask-rs is the one to open. If you are trying to understand how databases let readers and writers coexist, mvcc is the one, and its panic on a write-write conflict is the kind of thing you want to see in a small program rather than infer from a paper. If you are writing a parser, expr-eval shows you the shortest correct version of operator precedence, before you add the abstract syntax tree you will eventually want.

The three also form a reasonable progression for someone new to Rust specifically, since the data structures are ordinary ones: a BTreeMap and a file for the bitcask engine, a BTreeMap and an AtomicU64 for the MVCC store, and a tokenizer plus recursion for the evaluator.

There are no release tags and the repository listing does not state a licence, so this is a repository to read rather than to depend on. It is also a repository with a clear age: the last push was on 2026-08-27, and nothing in the tree suggests active maintenance beyond that.

The expression evaluator avoids an AST with one precedence trick

The expr-eval project is a small arithmetic expression evaluator based on operator-precedence parsing, and the interesting thing about it is what it does not build.

A Tokenizer turns the input string into a stream of Tokens, which are numbers, operators and parentheses. Then Expr::compute_expr recursively evaluates left to right, and only descends into a sub-expression when the next operator binds tighter than the current minimum precedence, min_prec.

That is the entire algorithm, and the associativity handling is where the cleverness is. Right-associative operators, like the exponentiation operator, are handled by not bumping min_prec on recursion, while left-associative ones bump it by one.

The README says this one trick is what makes the whole algorithm fit in about 150 lines without a separate abstract syntax tree. That is a fair characterisation. The conventional approach is to parse into an AST and then walk it, which means two passes, two data structures, and a second function to write. The precedence-climbing approach evaluates as it parses, so the stack of pending operators is the only extra state.

The trade is that you cannot inspect the parsed form. There is no tree to print, no tree to walk for a different evaluation order, and no easy place to attach source positions for error messages. If you are learning how expression parsers work, this is the minimum viable version and it teaches the mechanism. If you are building something that needs an AST, you will want to write one.

The three projects together form a decent syllabus, incidentally. One is about durable storage, one is about concurrency, one is about parsing, and all three are small enough to read end to end.

Editorial conclusion

Adopt rust-practice if you want a readable reference implementation of a specific storage idea in Rust, since each project is small enough to read in an evening and the explanations cover the mechanism, the trade-off and the failure mode. Skip it if you need production-ready storage, because these are learning projects with no dependency policy, no release tags and an unstated licence. Verify first which of the three matches the concept you are trying to learn, and read the panic behaviour in the MVCC project before copying its write path, because the serialization error is a deliberate design choice rather than an oversight.

Frequently asked questions

What projects are in the rosedblabs/rust-practice repository?

Three: mini-bitcask-rs, a minimal Bitcask-model key-value disk storage engine with log-structured set, get, delete, scan and merge in a few hundred lines; expr-eval, an arithmetic expression parser and evaluator built around operator precedence in about 200 lines; and mvcc, a minimal Multi-Version Concurrency Control implementation with commit, rollback and set, get, delete and scan.

How does the mini-bitcask-rs project store deletes?

With a tombstone. The log entry format is key_len(4B) | value_len_or_tombstone(4B) | key | value, and a negative length marks a tombstone. Deletes are therefore appends like any other write, and old versions of a key become dead space until a merge rewrites only the live entries into a fresh file.

How does the mvcc project handle concurrent writes to the same key?

A write checks the latest version of the key first, and if that version is not visible to the current transaction, meaning another concurrent transaction touched it, it panics with a serialization error instead of silently overwriting. Visibility is decided by whether the version's writer was in the set of active transactions when this transaction began.

Does the expr-eval project build an abstract syntax tree?

No. It uses operator-precedence parsing where Expr::compute_expr evaluates recursively and only descends into a sub-expression when the next operator binds tighter than the current minimum precedence. Right-associative operators do not bump min_prec on recursion while left-associative ones do, which is what keeps it to about 150 lines.

Official sources

  1. Issues
  2. README
  3. rosedblabs/rust-practice on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/rosedblabs-rust-practice.svg)](https://hysenlabs.com/projects/rosedblabs-rust-practice)

Was this useful?