hyphae
A lock-free reactive programming library for Rust.
Features
- Lock-free - Atomic updates via
arc-swap, no blocking - Type-safe - Compile-time checking with heterogeneous cell combinations
- Thread-safe - Safe concurrent access across threads
- Dependency tracking - Inspect and visualize cell relationships
Quick Start
use ;
let x = new;
let y = new;
// join is stateful and returns a Cell. Chaining .map fuses into the
// join's installed callback when materialized; .materialize() compiles
// the chain into a cell you can subscribe to.
let sum = x.join.map.materialize;
// Subscribe to changes
let _guard = sum.subscribe;
x.set; // prints "Sum: 30"
Pipelines vs Cells
Pure operators (map, filter, try_map, tap, map_ok, map_err,
catch_error, unwrap_or) return a Pipeline — an uncompiled chain that
fuses closures at compile time. Call .materialize() to compile the chain
into a Cell you can subscribe to. There is no Pipeline::subscribe by
design: callers make the memoization decision explicit.
Stateful operators (scan, debounce, throttle, buffer_*, pairwise,
window, distinct*, sample, delay, take, first, last, merge,
merge_map, switch_map, with_latest_from, zip, join) return cells
directly — they hold per-subscription state, so memoization is unavoidable.
Operators
Transform, combine, and filter reactive streams. Pure operators below
(map, filter, catch_error) return pipelines — call .materialize()
when you need a cell. Stateful operators (scan, debounce, throttle)
return cells directly.
use Duration;
use ;
let doubled = x.map.materialize;
let filtered = x.filter.materialize;
let running_sum = numbers.scan;
let debounced = input.debounce;
let throttled = input.throttle;
let safe = fallible.catch_error.materialize;
Reactive Collections
use ;
let users = new;
let admin = users.get; // reactive cell
users.insert;
assert!; // updates automatically
Map Queries vs CellMaps
Pure CellMap operators (inner_join, left_join, project, select,
count_by, group_by, ...) return uncompiled MapQuery plan nodes — not
CellMaps. Plans compose: any plan or CellMap can feed any other
operator's input. Call .materialize() to allocate ONE output CellMap
with ONE subscription per root source, replacing what would otherwise be N
intermediate maps for an N-stage chain.
MapQuery plan nodes are not Clone (mirroring Pipeline). To share work
across consumers, materialize once and clone the resulting CellMap.
use ;
let users = new;
let scores = new;
users.insert;
scores.insert;
// Chained operators return plan nodes — no intermediate CellMap
// until materialize().
let view = users
.clone
.inner_join
.project
.materialize;
assert!;
Async Support
use ;
let cell = new;
let mut stream = cell.to_stream;
while let Some = stream.next.await
Requires the async feature flag.
Performance Monitoring
use Duration;
use Cell;
let cell = with_metrics;
cell.on_slow_subscriber;
License
MIT OR Apache-2.0