Reactive Primitives API

Orlando provides Signal and Stream types for reactive programming. These are currently Rust-only APIs.

Signal<T>

A time-varying value with automatic change propagation. When a source signal changes, all derived signals update automatically.

Signal::new(value)

Create a signal with an initial value.

#![allow(unused)]
fn main() {
use orlando_transducers::signal::Signal;

let counter = Signal::new(0_i32);
}

.get()

Get the current value. Returns Ref<T> (smart pointer).

#![allow(unused)]
fn main() {
let val = counter.get();
assert_eq!(*val, 0);
}

.set(value)

Set a new value, notifying all subscribers.

#![allow(unused)]
fn main() {
counter.set(42);
assert_eq!(*counter.get(), 42);
}

.update(f)

Update the value by applying a function.

#![allow(unused)]
fn main() {
counter.update(|n| n + 1);
assert_eq!(*counter.get(), 43);
}

.subscribe(f)

Subscribe to value changes. Returns a Subscription that unsubscribes when dropped.

#![allow(unused)]
fn main() {
let _sub = counter.subscribe(|val| {
    println!("Counter is now: {}", val);
});

counter.set(10);  // prints: Counter is now: 10
}

.map(f)

Create a derived signal that auto-updates when the source changes.

#![allow(unused)]
fn main() {
let celsius = Signal::new(100.0_f64);
let fahrenheit = celsius.map(|c| c * 9.0 / 5.0 + 32.0);

assert_eq!(*fahrenheit.get(), 212.0);

celsius.set(0.0);
assert_eq!(*fahrenheit.get(), 32.0);  // auto-updated
}

.combine(other, f)

Combine two signals into a derived signal.

#![allow(unused)]
fn main() {
let width = Signal::new(800_u32);
let height = Signal::new(600_u32);

let area = width.combine(&height, |w, h| w * h);
assert_eq!(*area.get(), 480_000);

width.set(1920);
assert_eq!(*area.get(), 1_152_000);  // auto-updated
}

.fold(stream, init, f)

Fold a stream's events into this signal's value.

#![allow(unused)]
fn main() {
use orlando_transducers::stream::Stream;

let counter = Signal::new(0_i32);
let clicks = Stream::new();

counter.fold(&clicks, 0, |count, _: &()| count + 1);

clicks.emit(());
clicks.emit(());
assert_eq!(*counter.get(), 2);
}

Stream<T>

A push-based event stream for discrete events.

Stream::new()

Create an empty stream.

#![allow(unused)]
fn main() {
use orlando_transducers::stream::Stream;

let events = Stream::<String>::new();
}

.emit(value)

Push a value to all subscribers.

#![allow(unused)]
fn main() {
events.emit("hello".into());
}

.subscribe(f)

Listen for events. Returns StreamSubscription that unsubscribes when dropped.

#![allow(unused)]
fn main() {
let _sub = events.subscribe(|msg| {
    println!("Received: {}", msg);
});

events.emit("test".into());  // prints: Received: test
}

.map(f)

Transform each event.

#![allow(unused)]
fn main() {
let raw = Stream::new();
let upper = raw.map(|s: String| s.to_uppercase());

upper.subscribe(|s| println!("{}", s));
raw.emit("hello".into());  // prints: HELLO
}

.filter(pred)

Only pass events matching the predicate.

#![allow(unused)]
fn main() {
let numbers = Stream::new();
let evens = numbers.filter(|n: &i32| n % 2 == 0);

evens.subscribe(|n| println!("Even: {}", n));
numbers.emit(1);  // nothing
numbers.emit(2);  // prints: Even: 2
numbers.emit(3);  // nothing
numbers.emit(4);  // prints: Even: 4
}

.take(n)

Take only the first n events, then stop.

#![allow(unused)]
fn main() {
let events = Stream::new();
let first3 = events.take(3);

first3.subscribe(|v| println!("{}", v));
events.emit(1);  // prints: 1
events.emit(2);  // prints: 2
events.emit(3);  // prints: 3
events.emit(4);  // nothing (taken 3 already)
}

.merge(other)

Merge two streams into one.

#![allow(unused)]
fn main() {
let keyboard = Stream::new();
let mouse = Stream::new();

let input = keyboard.merge(&mouse);
input.subscribe(|event| handle(event));

keyboard.emit(KeyEvent::Press('a'));
mouse.emit(MouseEvent::Click(100, 200));
// Both arrive at the merged subscriber
}

.fold(init, f)

Fold events into a Signal, bridging discrete events to continuous state.

#![allow(unused)]
fn main() {
let measurements = Stream::new();
let sum = measurements.fold(0.0_f64, |acc, val: &f64| acc + val);

measurements.emit(10.0);
measurements.emit(20.0);
assert_eq!(*sum.get(), 30.0);
}

Subscription Lifecycle

Subscriptions are cleaned up automatically when dropped:

#![allow(unused)]
fn main() {
let sig = Signal::new(0);

{
    let _sub = sig.subscribe(|v| println!("{}", v));
    sig.set(1);  // prints: 1
}
// _sub dropped — subscription removed

sig.set(2);  // no output
}

Explicit cleanup:

#![allow(unused)]
fn main() {
let sub = stream.subscribe(|e| handle(e));
drop(sub);  // explicitly unsubscribe
}