lazily
Lazy reactive signals for Rust — dependency tracking and cache invalidation.
Overview
lazily provides two core primitives for lazy reactive computation:
- Slot — a lazily-computed cached value that automatically tracks dependencies
- Cell — a mutable value that invalidates dependent Slots when changed
Values are lazy: dependents are cleared on invalidation but only recomputed when accessed. This contrasts with eager "signal" systems that recompute immediately.
Usage
use Context;
let ctx = new;
// Create a mutable cell
let counter = ctx.cell;
// Create a derived slot (automatically tracks dependencies)
let doubled = ctx.slot;
assert_eq!;
// Mutate the cell — dependents are cleared (not recomputed yet)
ctx.set_cell;
// Slot recomputes lazily on next access
assert_eq!;
Core Concepts
Slot
A SlotHandle<T> wraps a compute function Fn(&Context) -> T. The result is cached after first access. Dependencies are discovered automatically via a thread-local tracking stack — any Slot or Cell accessed during computation becomes a dependency.
When a dependency is invalidated, the Slot clears its cached value. It does not recompute until ctx.get() is called again.
Cell
A CellHandle<T> holds a mutable value. ctx.set_cell() compares old and new values via PartialEq — if unchanged, no invalidation occurs. If changed, all dependent Slots are recursively cleared.
Context
Context owns all Slots and Cells. It manages the dependency graph and provides the API surface for creating, reading, and mutating reactive values.
Design
- Interior mutability via
RefCell(single-threaded) - Thread-local tracking stack for automatic dependency discovery
- Dynamic dependency graphs — edges re-discovered on each recomputation
- Zero external dependencies
Related
- lazily-zig — Zig implementation with FFI support
- lazily-py — Python implementation with context-as-dict
License
MIT