Crate hotdrink_rs[][src]

HotDrink implemented in Rust.

HotDrink lets you describe relations between values declaratively and how to enforce them, and can then automatically do so when the value of a variable changes.


Before getting started, here is a quick introduction to the terminology and how it works. A Component is a set of variables with a set of Constraints between them. A Constraint consists of a set of Methods that are essentially functions that enforce the constraint by reading from some subset of the variables of the Component and writing to another. Components can be gathered in a ConstraintSystem, which provides an API for interacting with multiple Components at once, such as update.


A component is a collection of variables and constraints between them that should be enforced. One can easily be created by using the component! macro, as shown in the example below.


A constraint represents a relation between variables we want to maintain. It contains a collection of constraint satisfaction methods that describe the different ways to do so. In the example, we want the relation a + b = c to hold at all times. One way to enforce it is to re-compute a + b and set c to that value.


A constraint satisfaction method describes one way to enforce a constraint. It reads the values of some variables, and write to others.


Add the following to your Cargo.toml:

hotdrink-rs = "0.1.1"

Then you are ready to begin!

use hotdrink_rs::{component, model::ConstraintSystem, ret, Event};

// Define a set of variables and relations between them
let mut component = component! {
    // Define a component `Component`.
    component Component {
        // Define variables and their default values.
        // The value can be omitted for any type that implements `Default`.
        let a: i32 = 0, b: i32, c: i32 = 3;
        // Define a constraint `Sum` that must hold between variables.
        constraint Sum {
            // Provide three ways to enforce the constraint.
            // Only one will be selected, so each one *MUST* enforce the constraint.
            abc(a: &i32, b: &i32) -> [c] = ret![*a + *b];
            acb(a: &i32, c: &i32) -> [b] = ret![*c - *a];
            bca(b: &i32, c: &i32) -> [a] = ret![*c - *b];

// Describe what should happen when `a` changes.
component.subscribe("a", |event| match event {
    Event::Pending => println!("A new value for `a` is being computed"),
    Event::Ready(value) => println!("New value for `a`: {}", value),
    Event::Error(errors) => println!("Computation for `a` failed: {:?}", errors),

// Change the value of `a`
component.set_variable("a", 3);

// Enforce all the constraints by selecting a method for each one,
// and then executing the methods in topological order.

// Add the component to a constraint system.
// One constraint system can contain many components.
let mut cs = ConstraintSystem::new();

// Update every component in the constraint system.


The project uses multiple nightly features, and must be built using nightly Rust. I recommend using rustup, which can be downloaded here.

The examples in ./examples can then be run with cargo run --example <name>.



Builders for creating components manually, a new variant of the component macro, and value-experiments for allowing mutation of values in the constraint system.


Example constraint systems and components.


Macros and types for easily generating components.


Data types used for representing constraint systems, with the most important ones being the following:


Algorithms used in planning and solving of constraint systems.


Types and functions for executing plans generated by a planners.


Thread pool traits and implementations.


Utility functions.



A macro for specifying components.


A macro for automatically generating an enum that has a variant for each type to use in a constraint system. It will also implement From and TryInto implementations for each of these variants.


Turns a list of inputs into a failed MethodResult. This can be used defining methods in components with component!.


Turns a list of inputs into a successful MethodResult. This can be used defining methods in components with component!. To make returning the possible values of a sum type used in a Component easier, it will automatically call Into::into on each argument.



An event from the constraint system.