| Crates.io | spark-signals |
| lib.rs | spark-signals |
| version | 0.1.2 |
| created_at | 2026-01-21 21:58:52.217354+00 |
| updated_at | 2026-01-26 01:25:24.441771+00 |
| description | A standalone reactive signals library for Rust - fine-grained reactivity for any application |
| homepage | |
| repository | https://github.com/RLabs-Inc/spark-signals |
| max_upload_size | |
| id | 2060183 |
| size | 696,993 |
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.
derived!, effect!, and prop! macros make Rust feel like a scripting language.TrackedSlotArray and TrackedSlot for fine-grained ECS and layout optimization.[dependencies]
spark-signals = "0.1.2"
Spark Signals provides macros that handle Rc cloning and closure moving for you. Just list your dependencies and write code.
use spark_signals::{signal, derived};
fn main() {
let width = signal(10);
let height = signal(20);
// "Derived depends on width and height"
// The macro handles cloning 'width' and 'height' for the closure
let area = derived!(width, height => width.get() * height.get());
println!("Area: {}", area.get()); // 200
width.set(5);
println!("Area: {}", area.get()); // 100
}
Side effects that run automatically when dependencies change.
use spark_signals::{signal, effect};
let count = signal(0);
// "Effect reads count"
effect!(count => {
println!("Count changed to: {}", count.get());
});
count.set(1); // Prints: Count changed to: 1
Create getters that capture signals effortlessly.
use spark_signals::{signal, prop, reactive_prop};
let first = signal("Sherlock");
let last = signal("Holmes");
// Create a prop getter
let full_name_prop = prop!(first, last => format!("{} {}", first.get(), last.get()));
// Convert to derived for uniform access
let full_name = reactive_prop(full_name_prop);
println!("{}", full_name.get()); // Sherlock Holmes
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 spark_signals::{slot, signal, PropValue};
let s = slot::<i32>(None);
let sig = signal(42);
// Bind to a signal
s.bind(PropValue::from_signal(&sig));
assert_eq!(s.get(), Some(42));
// Bind to a static value
s.bind(PropValue::Static(100));
assert_eq!(s.get(), Some(100));
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 spark_signals::{tracked_slot, dirty_set};
let dirty = dirty_set();
// Slot ID 0 reports to 'dirty' set on change
let width = tracked_slot(Some(10), dirty.clone(), 0);
width.set_value(20);
assert!(dirty.borrow().contains(&0)); // We know ID 0 changed!
This library implements the "Push-Pull" reactivity model:
DIRTY or MAYBE_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.
MIT