crdt-kit
Conflict-free Replicated Data Types for Rust
Build offline-first, real-time, and distributed applications that just work.
Documentation • Crate • Examples • Contributing
Why crdt-kit?
Traditional sync solutions break when devices go offline. CRDTs solve this at the data structure level — every replica can be updated independently, and merges always converge to the same result, guaranteed by math, not by servers.
crdt-kit is built specifically for resource-constrained, latency-sensitive environments where existing solutions (Automerge, Yjs) add too much overhead:
+-----------+ +-----------+ +-----------+
| Device A | | Device B | | Device C |
| (offline)| | (offline)| | (offline)|
+-----+-----+ +-----+-----+ +-----+-----+
| | |
| local edits | local edits | local edits
| | |
+--------+--------+--------+--------+
| |
v v
+-----------+ +-----------+
| merge | | merge |
+-----------+ +-----------+
| |
v v
Same state! Same state! <-- Strong Eventual Consistency
Key Advantages
crdt-kit |
Automerge | Yjs | |
|---|---|---|---|
| Zero dependencies (core) | Yes | No (30+) | No (N/A — JS) |
no_std / embedded |
Yes | No | No |
| WASM-ready | Yes | Partial | Native JS |
| Delta sync | Yes | Yes | Yes |
| Serde integration | Yes | Custom | N/A |
| Pure Rust | Yes | Yes | No (JS) |
| Binary size (release) | ~50 KB | ~2 MB | N/A |
Built For
| Environment | Why it matters |
|---|---|
| IoT / Embedded | no_std support — runs on bare metal, Raspberry Pi, ESP32 |
| Mobile apps | Offline-first with automatic conflict resolution on reconnect |
| Edge computing | Delta sync minimizes bandwidth between edge nodes |
| P2P networks | No central server needed — every peer is equal |
| Real-time collaboration | Concurrent edits merge without coordination |
| WASM / Browser | First-class WebAssembly bindings for web apps |
Quick Start
[]
= "0.2"
use *;
// Two devices, working offline
let mut phone = new;
phone.increment;
phone.increment;
let mut laptop = new;
laptop.increment;
// When they reconnect — merge. Always converges.
phone.merge;
assert_eq!;
Available CRDTs
Counters
| Type | Description | Real-world use |
|---|---|---|
GCounter |
Grow-only counter | Page views, IoT sensor events, download counts |
PNCounter |
Increment & decrement | Inventory stock, likes/dislikes, seat reservations |
Registers
| Type | Description | Real-world use |
|---|---|---|
LWWRegister |
Last-writer-wins | User profile fields, config settings, GPS location |
MVRegister |
Multi-value (shows conflicts) | Collaborative fields, version tracking |
Sets
| Type | Description | Real-world use |
|---|---|---|
GSet |
Grow-only set | Seen message IDs, tags, audit logs |
TwoPSet |
Add & permanent remove | Blocklists, revoked tokens |
ORSet |
Add & remove freely | Shopping carts, todo lists, chat members |
Sequences
| Type | Description | Real-world use |
|---|---|---|
Rga |
Replicated Growable Array | Playlists, kanban boards, ordered lists |
TextCrdt |
Collaborative text | Google Docs-style editing, shared notes |
Traits
| Trait | Description |
|---|---|
Crdt |
Core merge semantics (commutative, associative, idempotent) |
DeltaCrdt |
Efficient delta sync — send only what changed |
Real-World Example: Distributed E-Commerce
A complete example showing CRDTs powering an offline-first e-commerce system across multiple stores:
use *;
// === Distributed Inventory ===
// Each store manages stock independently, even offline
let mut store_nyc = new;
let mut store_la = new;
// NYC receives 50 units, sells 12
for _ in 0..50
for _ in 0..12
// LA receives 30 units, sells 8
for _ in 0..30
for _ in 0..8
// HQ syncs both — always correct, no conflicts
store_nyc.merge;
assert_eq!; // (50-12) + (30-8) = 60
// === Shopping Cart (add wins over remove) ===
let mut cart_phone = new;
let mut cart_laptop = new;
cart_phone.insert;
cart_phone.insert;
cart_laptop.insert;
// User removes Headphones on phone, re-adds on laptop
cart_phone.remove;
cart_laptop.insert;
// Sync: add wins! No lost items.
cart_phone.merge;
assert!;
assert!;
// === Product Price (last write wins) ===
let mut price_admin = with_timestamp;
let mut price_promo = with_timestamp;
price_admin.merge;
assert_eq!; // promo wins (later timestamp)
// === Collaborative Product Description ===
let mut desc_alice = new;
desc_alice.insert_str;
let mut desc_bob = desc_alice.fork;
desc_alice.insert_str;
desc_bob.insert_str;
desc_alice.merge;
// Both edits preserved — deterministic convergence
// === Delta Sync (bandwidth-efficient) ===
let mut views_edge = new;
for _ in 0..1000
let mut views_cloud = new;
// Send only the diff, not the full state
let delta = views_edge.delta;
views_cloud.apply_delta;
assert_eq!;
See the full runnable example:
Feature Flags
| Feature | Default | Description |
|---|---|---|
std |
Yes | Standard library support |
serde |
No | Serialize / Deserialize for all types |
wasm |
No | WebAssembly bindings via wasm-bindgen |
# Embedded / no_std (bare metal, ESP32, Raspberry Pi Pico)
= { = "0.2", = false }
# With serde (JSON, MessagePack, Bincode, etc.)
= { = "0.2", = ["serde"] }
# For web applications (WASM)
= { = "0.2", = ["wasm"] }
Performance
Measured with Criterion on optimized builds:
| Operation | Time | Throughput |
|---|---|---|
| GCounter increment x1000 | 53 µs | ~19M ops/sec |
| GCounter merge 10 replicas | 1.1 µs | ~9M merges/sec |
| GCounter merge 100 replicas | 17.8 µs | — |
| PNCounter inc+dec x1000 | 60 µs | ~16M ops/sec |
| ORSet insert x1000 | 187 µs | ~5M ops/sec |
| ORSet merge 500+500 elements | 191 µs | — |
| GSet merge 1000+1000 elements | 102 µs | — |
| LWWRegister merge 100 replicas | 11.5 µs | ~8M merges/sec |
Guarantees
All CRDTs satisfy Strong Eventual Consistency (SEC):
| Property | Meaning | Why it matters |
|---|---|---|
| Commutativity | merge(a, b) == merge(b, a) |
Order of sync doesn't matter |
| Associativity | merge(a, merge(b, c)) == merge(merge(a, b), c) |
Group syncs however you want |
| Idempotency | merge(a, a) == a |
Safe to retry — no duplicates |
Verified by 132 tests (111 unit + 9 integration + 12 doctests).
Architecture
crdt-kit
├── Crdt trait — core merge semantics
├── DeltaCrdt trait — delta sync extension
├── GCounter — grow-only counter + DeltaCrdt
├── PNCounter — positive-negative counter + DeltaCrdt
├── LWWRegister<T> — last-writer-wins register
├── MVRegister<T> — multi-value register
├── GSet<T> — grow-only set
├── TwoPSet<T> — two-phase set
├── ORSet<T> — observed-remove set + DeltaCrdt
├── Rga<T> — replicated growable array
├── TextCrdt — collaborative text
└── wasm::* — WASM bindings (opt-in)
Roadmap
- G-Counter, PN-Counter
- LWW-Register, MV-Register
- G-Set, 2P-Set, OR-Set
- RGA List (ordered sequence)
- Text CRDT (collaborative text editing)
-
no_stdsupport (embedded / bare metal) -
serdeserialization support - Delta-state optimization
- WASM bindings
- Persistent storage adapters (sled, SQLite)
- Network transport layer (TCP, WebSocket, QUIC)
- Benchmarks against Automerge / Yrs
Contributing
Contributions are welcome! Please read CONTRIBUTING.md before submitting a pull request.
License
Dual-licensed under your choice of:
- MIT — LICENSE-MIT
- Apache 2.0 — LICENSE-APACHE