Crate depends

source ·
Expand description

Depends

A library for ergonomic, performant, incremental computation between arbitrary types.

For more information, see:

Motivation

Many applications which respond to changes from multiple input sources benefit from the use of dependency graphs as code structure. By breaking complex states down in to small pieces of testable, composable logic, scaling and maintaining applications becomes much easier over time. Additionally, incremental computation allows results of previous calculations to be reused where possible, improving overall efficiency and performance.

Depends aims to present the smallest possible API surface for building minimal runtime-overhead dependency graphs in Rust, whilst leveraging the compile-time guarantees of the type-system.

// Below are input nodes, which are nodes which take new values from
// outside the graph.
// It's not common to use primitives, but they make for a simple example.
let a = InputNode::new(7_i64);
let b = InputNode::new(6_i32);

// Derived nodes take their value from other nodes (either input or
// derived). Note that we can combine _any_ type of node, providing
// they're compatible with the dependencies (`TwoNumbers`) and operation
// (`Multiply`).
let c = DerivedNode::new(
    TwoNumbers::init(Rc::clone(&a), Rc::clone(&b)),
    Multiply,
    0_i64,
);

// A visitor tracks which nodes have been visited during a resolve.
let mut visitor = HashSetVisitor::new();

// Resolve the graph!
// `resolve_root` will clear the visitor before returning, readying it
// for the next resolution.
// This can fail if there are cycles in the graph or an existing read
// reference is being held.
assert_eq!(c.resolve_root(&mut visitor).unwrap().value().clone(), 42);

// Nodes which have an edge to dependencies which are updated between
// resolves will recalculate their state on-demand. Others will return
// a cached value. This is known as incremental computation, and can
// vastly improve performance of complex calculations.
a.update(70).unwrap();

// Any dependent values will be updated next time the graph is resolved.
assert_eq!(c.resolve_root(&mut visitor).unwrap().value().clone(), 420);

Re-exports

  • pub use identifiable::next_node_id;

Modules

Structs

  • A read reference to the resolved state of a Dependency
  • Wraps a dependency and tracks the hashed value each time it’s resolved. This allows the resolver to know if a dependency is ‘dirty’ from the perspective of the Dependee.
  • Derived Node
  • Input Node
  • A wrapper for some value T, tracking some context around the value’s computation state.

Enums

  • Used to ensure that pending data is resolved at most once between calls to update.
  • A Hash of the current node state used to signal whether a dependent node needs to update its internal state.

Traits

  • For the majority of cases, this trait can be ignored, and a default implementation (no-op) can be used.
  • GraphCreategraphviz
    Convenience so that types which derive Graph can be easily accessed.
  • A unique number derived from the internal state of a node.
  • A unique integer value assigned to each node created in a particular runtime, allowing a Visitor to track visited nodes when resolving graphs.
  • For any dependee’s dependencies (or single Dependency), this is used to check whether previously observed values have changed, indicating its stored value needs to be recomputed.
  • A string name for each graph node, useful for rendering graph visualisations.
  • A Depth-first search resolver, used to recursively pass a Visitor through a graph, updating dependencies.
  • Describe how to update an InputNode value, and how that mutates the internal state. Correct implementation of this trait requires that any temporary state tracked is cleared up when implementing Clean.
  • A collection passed in to a graph, tracking the identifiers of each nodes to avoid traversing

Type Aliases