tincan 0.3.0

A lightweight reactive state management library for Rust.
Documentation
//! # Tincan - A Fast, Lock-Free Reactive Primitives Library
//!
//! Tincan provides fine-grained reactivity with signals, computed values (memos),
//! and side effects. It's designed for building reactive applications with automatic
//! dependency tracking and efficient updates.
//!
//! ## Overview
//!
//! Tincan uses a **context object pattern** where all reactive primitives are created
//! through a [`Scope`]. This provides explicit ownership, automatic cleanup, and
//! lock-free performance for single-threaded contexts.
//!
//! ## Core Concepts
//!
//! - **[`Scope`]**: The reactive context that owns all reactive primitives
//! - **[`Signal`]**: Mutable reactive state that notifies observers when changed
//! - **[`Memo`]**: Computed values that cache results and only recompute when dependencies change
//! - **[`Effect`]**: Side effects that automatically re-run when dependencies change
//!
//! ## Quick Start
//!
//! ```rust
//! use tincan::Scope;
//!
//! // Create a reactive scope
//! let cx = Scope::new();
//!
//! // Create a signal (reactive state)
//! let count = cx.signal(0);
//!
//! // Create a memo (computed value)
//! let doubled = cx.memo({
//!     let count = count.clone();
//!     move || count.get() * 2
//! });
//!
//! // Create an effect (side effect)
//! let count_clone = count.clone();
//! let doubled_clone = doubled.clone();
//! cx.effect(move || {
//!     println!("Count: {}, Doubled: {}", count_clone.get(), doubled_clone.get());
//! });
//! // Prints: Count: 0, Doubled: 0
//!
//! // Update the signal
//! count.set(5);
//! // Effect runs again, prints: Count: 5, Doubled: 10
//! ```
//!
//! ## Features
//!
//! ### Automatic Dependency Tracking
//!
//! Effects and memos automatically track which signals they read:
//!
//! ```rust
//! use tincan::Scope;
//!
//! let cx = Scope::new();
//! let first = cx.signal("John");
//! let last = cx.signal("Doe");
//!
//! let full_name = cx.memo({
//!     let first = first.clone();
//!     let last = last.clone();
//!     move || format!("{} {}", first.get(), last.get())
//! });
//!
//! assert_eq!(full_name.get(), "John Doe");
//!
//! first.set("Jane");
//! assert_eq!(full_name.get(), "Jane Doe");
//! ```
//!
//! ### Efficient Updates
//!
//! Memos cache their values and only recompute when dependencies change:
//!
//! ```rust
//! use tincan::Scope;
//! use std::cell::Cell;
//! use std::rc::Rc;
//!
//! let cx = Scope::new();
//! let input = cx.signal(5);
//! let compute_count = Rc::new(Cell::new(0));
//!
//! let expensive = cx.memo({
//!     let input = input.clone();
//!     let compute_count = Rc::clone(&compute_count);
//!     move || {
//!         compute_count.set(compute_count.get() + 1);
//!         input.get() * 2
//!     }
//! });
//!
//! assert_eq!(expensive.get(), 10);
//! assert_eq!(compute_count.get(), 1);
//!
//! // Reading again uses cached value
//! assert_eq!(expensive.get(), 10);
//! assert_eq!(compute_count.get(), 1); // Not recomputed!
//!
//! // Only recomputes when input changes
//! input.set(10);
//! assert_eq!(expensive.get(), 20);
//! assert_eq!(compute_count.get(), 2); // Recomputed once
//! ```
//!
//! ### Isolation and Cleanup
//!
//! Scopes provide automatic cleanup and isolation:
//!
//! ```rust
//! use tincan::Scope;
//!
//! // Each scope is completely independent
//! let cx1 = Scope::new();
//! let signal1 = cx1.signal(1);
//!
//! let cx2 = Scope::new();
//! let signal2 = cx2.signal(2);
//!
//! // They don't interfere with each other
//! assert_eq!(signal1.get(), 1);
//! assert_eq!(signal2.get(), 2);
//!
//! // Dropping the scope cleans up all resources
//! drop(cx1);
//! // signal1 is now unusable (would panic if accessed)
//! ```
//!
//! ### Signal Combinators
//!
//! Transform and combine signals:
//!
//! ```rust
//! use tincan::Scope;
//!
//! let cx = Scope::new();
//! let celsius = cx.signal(0);
//!
//! // Map: Transform a signal
//! let fahrenheit = celsius.map(|c| c * 9 / 5 + 32);
//! assert_eq!(fahrenheit.get(), 32);
//!
//! celsius.set(100);
//! assert_eq!(fahrenheit.get(), 212);
//!
//! // Zip: Combine two signals
//! let width = cx.signal(10);
//! let height = cx.signal(5);
//! let area = width.clone().zip(height).map(|(w, h)| w * h);
//! assert_eq!(area.get(), 50);
//! ```
//!
//! ### Watching for Changes
//!
//! React to specific signal changes:
//!
//! ```rust
//! use tincan::Scope;
//! use std::cell::Cell;
//! use std::rc::Rc;
//!
//! let cx = Scope::new();
//! let count = cx.signal(0);
//! let call_count = Rc::new(Cell::new(0));
//!
//! let call_count_clone = Rc::clone(&call_count);
//! let _guard = count.watch(move |_value| {
//!     call_count_clone.set(call_count_clone.get() + 1);
//! });
//!
//! assert_eq!(call_count.get(), 1); // Called immediately
//!
//! count.set(5);
//! assert_eq!(call_count.get(), 2); // Called on change
//! ```
//!
//! ## Testing
//!
//! Scopes make testing easy:
//!
//! ```rust
//! use tincan::Scope;
//!
//! #[test]
//! fn test_counter() {
//!     let cx = Scope::new();
//!     let count = cx.signal(0);
//!     
//!     count.set(42);
//!     assert_eq!(count.get(), 42);
//! } // Scope dropped, everything cleaned up
//! ```
//!
//! ## Examples
//!
//! See the `examples/` directory for more complete examples:
//!
//! - `basic_signals.rs` - Simple signal operations
//! - `effects.rs` - Working with effects
//! - `memos.rs` - Computed values and caching
//!

pub mod effect;
pub mod memo;
pub mod runtime;
pub mod signal;

pub use effect::Effect;
pub use memo::Memo;
pub use runtime::Scope;
pub use signal::{Signal, WatchGuard};