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

Concurrency contracts

The synchronous API keeps thread sharing explicit. A regular Specification<T> does not require Send or Sync, so a local rule may use single-threaded state when that is the correct domain model.

Use SharedSpecification<T> when a rule must cross a thread boundary. Its constructor requires the concrete rule to be Send + Sync + 'static, and its cloneable wrapper uses an Arc internally:

The complete runnable version is dynamic_and_concurrency.rs.

The following snippet is illustrative in mdBook (the book test runner does not link standalone blocks against the workspace crate); the equivalent rustdoc example is compiled by Cargo’s doctest gate.

use specification_core::{SharedSpecification, Specification};

let rule = SharedSpecification::new(|value: &u64| *value % 2 == 0);
let worker_rule = rule.clone();

std::thread::spawn(move || assert!(worker_rule.is_satisfied_by(&2)))
    .join()
    .expect("worker thread should finish");

EvaluationContext<UserData> is immutable after construction. It can be moved or shared whenever UserData satisfies the corresponding Rust auto-traits. There is no global context registry.

Time-dependent rules receive their Clock explicitly. A mutable or shared clock is therefore an application-owned dependency: use an appropriate standard-library synchronization primitive and inject it into the rule. This keeps concurrent behavior visible in the type graph and makes deterministic tests possible with FixedClock.