Spark Signals ⚡️
A standalone reactive signals library for Rust. Fine-grained reactivity, zero-overhead, and TypeScript-like ergonomics.
Spark Signals is a high-performance Rust port of the @rlabs-inc/signals TypeScript library. It solves the "hard problems" of Rust reactivity—type erasure, circular dependencies, and borrow checking—while providing an API that feels like writing TypeScript.
Features
- ⚡️ Blazing Fast: Benchmarked at ~4ns reads, ~22ns writes. Optimized for game engines and TUIs.
- 🧠 TypeScript-like Ergonomics:
derived!,effect!, andprop!macros make Rust feel like a scripting language. - 🔄 Deep Reactivity:
TrackedSlotArrayandTrackedSlotfor fine-grained ECS and layout optimization. - 🛡️ Memory Safe: Automatic dependency tracking and cleanup with zero unsafe code in the hot path.
- 🔌 Framework Agnostic: Use it for UI, games, state management, or backend logic.
Installation
[]
= "0.1.2"
The "Pure Magic" Syntax
Spark Signals provides macros that handle Rc cloning and closure moving for you. Just list your dependencies and write code.
Signals & Deriveds
use ;
Effects
Side effects that run automatically when dependencies change.
use ;
let count = signal;
// "Effect reads count"
effect!;
count.set; // Prints: Count changed to: 1
Props (for Components)
Create getters that capture signals effortlessly.
use ;
let first = signal;
let last = signal;
// Create a prop getter
let full_name_prop = prop!;
// Convert to derived for uniform access
let full_name = reactive_prop;
println!; // Sherlock Holmes
Advanced Primitives
Slots & Binding
Slot<T> is a stable reference that can switch between static values, signals, or getters. Perfect for component inputs that might change source type at runtime.
use ;
let s = ;
let sig = signal;
// Bind to a signal
s.bind;
assert_eq!;
// Bind to a static value
s.bind;
assert_eq!;
Tracked Slots (Optimization)
TrackedSlot automatically reports changes to a shared DirtySet. This is critical for optimizing layout engines (like Taffy) or ECS systems where you only want to process changed items.
use ;
let dirty = dirty_set;
// Slot ID 0 reports to 'dirty' set on change
let width = tracked_slot;
width.set_value;
assert!; // We know ID 0 changed!
Architecture
This library implements the "Push-Pull" reactivity model:
- Push: When a signal changes, it marks dependents as
DIRTYorMAYBE_DIRTY. - Pull: When a derived is read, it re-executes only if its dependencies are dirty.
It uses a "Father State" pattern (inspired by ECS) where data lives in parallel arrays or stable slots, minimizing object allocation and pointer chasing.
License
MIT