[][src]Crate reax

Reax is a reactivity system for Rust that infers dependencies between functions.

Every Variable managed by reax is a node in a dependency graph. Changes to and usages of variables are tracked by the global ThreadRuntime which updates the graph to reflect actual variable accesses. There are two kinds of built-in variables:

  • Var which can be explicitly mutated.
  • ComputedVar which lazily computes its value with a function.

Users can listen to and react to changes of variables using .watch.

Critically, a ComputedVar will only re-compute when needed. If, when computing its value, a ComputedVar uses any other variable anywhere (directly or indirectly), changes to any of those upstream variables will automatically mark the computed variable as dirty and the variable's value will be recomputed the next time it is used.

Examples

Reax builds a model of how the variables in your program interact as it runs.

use reax::prelude::*;

// Create input variables.
let number = Var::new(1).with_label("number");
let name = Var::new("Sam").with_label("name");

// Create computed variables.
let formatted = (&number)
   .map(|x| format!("{}", x))
   .with_label("formatted");

let printout = computed! {
    output! text = String::new(); // Reuse a buffer

    text.clear();
    *text += *name.get();
    *text += " sees ";
    *text += formatted.get().as_str();
}.with_label("printout");

// The computed variables haven't been used yet. Nothing is hooked-up.
assert_eq!(printout.node().depends_on(formatted.node()), false);

// Use the variables!
assert_eq!(printout.get().as_str(), "Sam sees 1");
number.set(42);
name.set("Reax");
assert_eq!(printout.get().as_str(), "Reax sees 42");

// Reax now knows how data moves through the variables!
assert_eq!(printout.node().depends_on(formatted.node()), true);

// Print a .dot visualization.
reax::ThreadRuntime::write_graphviz(std::io::stdout().lock()).unwrap();

We can see this example through reax's eyes:

%3 1 name 3 printout 1->3 0 number 2 formatted 0->2 2->3

Reax will only update computed variables when needed.

use reax::prelude::*;

let number = Var::new(0);
let bigger_number = (&number).map(|x| *x + 10);
let even_bigger_number = (&bigger_number).map(|x| *x + 100);
let times_called = Var::new(0);

// Set up a watcher to track how often bigger_number changes.
let mut eval = EagerCompute::new(());
eval.watch(&bigger_number, |_| {
   *times_called.mutate() += 1;
});

// The watcher is called once on creation.
assert_eq!(*times_called.get(), 1);

// Run the watcher. This is effectively a no-op since nothing has changed.
for _ in 0..100 { eval.tick(); }

// Update a variable.
number.set(1);

// Dependent variables are instantly dirty.
assert_eq!(bigger_number.node().is_dirty(), true);
assert_eq!(even_bigger_number.node().is_dirty(), true);

// Run the watcher again. This time it fires.
eval.tick();
assert_eq!(*times_called.get(), 2);

// even_bigger_number is still dirty since no one has used it yet.
assert_eq!(even_bigger_number.node().is_dirty(), true);

Here you can see how the variables downstream from number are all instantly marked when it changes. But they won't be recomputed until used:

%3 4 watcher 1 bigger_number 1->4 2 even_bigger_number 1->2 0 number 0->1 3 times_called

Reax has no built-in understanding of collections so you can use nested Vars to better control the "depth" of changes.

use reax::prelude::*;

// Create a list of variables.
let list = Var::new(Vec::new());
for x in 1..=3 {
    list.mutate().push(Var::new(x));
}

// Some computed properties:
let length = computed! { list.get().len() };
let sum = computed! {
    list.get().iter().map(|elem| *elem.get()).sum::<i32>()
};

// Make length and sum outdated by pushing an extra element.
list.mutate().push(Var::new(4));

// Update the length.
length.check(&mut ());

// Now only make sum outdated, and leave it that way.
list.get()[0].set(100);

Visualizing the runtime at the end of this example, you can see that only the sum is dirty. None of the list elements are dependencies of the list itself so any changes to them don't effect variables that never read them. And reax hasn't seen that the extra element will be used in the sum. It will find that out the next time the sum is computed.

%3 5 sum 4 length 6 extra_element 1 element 1->5 0 list 0->5 0->4 3 element 3->5 2 element 2->5

Modules

computed

All the types and traits needed for storing and updating lazily computed variables.

handler

Handlers for variables becoming dirty.

prelude

The types and macros you most likely want to use.

Macros

computed

A macro for conveniently creating ComputedVars.

computed_move

Like computed! but captures by move rather than by reference.

Structs

EagerCompute

A helper which eagerly updates any outdated variables it is given.

Node

A node handle used to communicate with a thread's reax runtime.

ThreadRuntime

The per-thread reax runtime responsible for tracking changes and dependencies.

Var

A mutable variable that is tracked by the reax runtime.

Traits

Variable

A trait implemented by any wrapped data which reax can manage.