rsconnect

Rust crate for fine-grained reactivity

Goal

This crate allows you to structure change propagation in your code based on graph of data nodes that depend on each other in automatically tracked dependency graph.

Suppose in our system we have data on the number of produced items, the cost of production for each, the number of items we sold, and how much each item is priced for. We would like to calculate total cost and revenue, and then profit out of those. The dependency graph would look like this:

items_produced item_cost items_sold item_price ⬂ ⬃ ⬂ ⬃ total_cost revenue ⬂ ⬃ profit

Every single time items_produced, item_cost, items_sold or item_price is updated, we'd like to recalculate total_cost, revenue and, then, profit. In addition, when profit is updated, we would like to print it with a simple println!. This is what the code for this would look like with rsconnect:

```rs let c = Connect::new();

let itemsproduced = c.observed(25); let itemssold = c.observed(20); let itemcost = c.observed(10.0); let itemprice = c.observed(13.0); let revenue = c.computed( move |c| c.get(itemprice) * *c.get(itemssold) as f32 ); let totalcost = c.computed( move |c| c.get(itemcost) * *c.get(itemsproduced) as f32 ); let profit = c.computed( move |c| c.get(revenue) - c.get(totalcost) );

c.effected( move |c| *c.get(profit), |profit| println!("Profit: {}", profit), ); ```

This immediately prints:

Profit: 10

In order to change value of any data node and thus trigger change propagation all the way to the underlying effects, one can simply call .set on any of the data nodes:

rs c.set(items_sold, 25);

This propagates changes to the correct computed and effected nodes and prints:

Profit: 75

Multiple changes may also be batched together so that recalculations only happen once, after the entire batch of nodes change:

rs c.batch(|c| { c.set(items_produced, 60); c.set(items_sold, 55); });

Which will print:

Profit: 115

Effected

Effected node consist of 2 parts, computed part and an effect:

rs c.effected( move |c| *c.get(profit), // computed part |profit| println!("Profit: {}", profit), // effect );

Computed part subscribes to its dependencies, just like a computed node. Effect is called immediately after recalculation and does not play part in the dependency graph, so it can read values in the graph without being subscribed to them. The result from the computed node is passed to the effect.

Additional notes

In order to optimize updates, new values are compared to old ones to determine whether further updates should be triggered. For that reason, the value of computed, observed and effected nodes is required to implement std::cmp::PartialEq trait. This is preferred because, when PartialEq values are used, there won't be unexpected updates. However, it's possible to create nodes that, when recalculated / set, will always be considered changed - for that, use .observed_any, .computed_any and .effected_any instead of .observed, .computed and .effected.

⚠️ Highly experimental

This crate is in active early development. There is likely undiscovered bugs and issues. There will also be breaking changes to API and how it works under the hood.