Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Core Concepts

The library applies the Specification Pattern: a small rule evaluates a candidate, and larger rules are built by composing those values. Rust’s trait system makes the static path the default; dynamic dispatch, heap allocation, and thread sharing are explicit choices.

Static and dynamic rules

Choose the smallest boundary that matches the problem:

NeedRust typeCost and guarantee
Known rules composed at compile timeAnd, Or, NotNo library allocation or trait-object dispatch
Homogeneous collectionAllOf, AnyOfBorrows a slice and short-circuits
Heterogeneous/runtime-selected rulesBoxedSpecification<T>Owns a Box<dyn Specification<T>>
Rule shared between threadsSharedSpecification<T>Owns an Arc and requires Send + Sync + 'static
use specification_core::{BoxedSpecification, Specification};

let rules: Vec<BoxedSpecification<u64>> = vec![
    BoxedSpecification::new(|value: &u64| *value > 0),
    BoxedSpecification::new(|value: &u64| *value % 2 == 0),
];

assert!(rules[1].is_satisfied_by(&42));

The complete thread-sharing scenario is in dynamic_and_concurrency.rs.

Boolean identities and short-circuiting

and, or, and not preserve left-to-right short-circuit evaluation. An empty AllOf is true, and an empty AnyOf is false. These identities let an application construct a rule from a possibly empty collection without special-case control flow.

Typed decisions

FirstMatch<T, Decision> is the explicit dynamic boundary for ordered routing. It stores heterogeneous rules with one concrete decision type, evaluates them in insertion order, and returns the first matching Option<&Decision>. Use decide_or when a fallback is required. See Decisions and Evaluation Context for a complete route-selection example.

Immutable context and dependencies

EvaluationContext<UserData> is an immutable input snapshot. Application data is typed through UserData; counters, flags, and timestamps have deliberate defaults. Time rules receive a Clock explicitly, so FixedClock can make a boundary deterministic in tests. There is no global context registry.

See Concurrency Contracts for Send + Sync behavior and Decisions and Evaluation Context for the built-in rules.

The runtime-neutral async layer is described in Async Specifications. Optional wire-format and predicate-macro support are described in Serialization and Macros.