2 unstable releases
| new 0.2.0 | Mar 2, 2026 |
|---|---|
| 0.1.0 | Feb 24, 2026 |
#399 in Data structures
160KB
4K
SLoC
Ooroo
A fast, compiled rule engine for Rust.
Ooroo is designed around a compile-once, evaluate-many architecture. Rulesets are compiled into an optimized, immutable execution structure that can be shared across threads via Arc and evaluated concurrently with zero synchronization overhead.
Installation
Add to your Cargo.toml:
[dependencies]
ooroo = "0.1"
Quick Start
use ooroo::{RuleSetBuilder, Context, field, rule_ref};
let ruleset = RuleSetBuilder::new()
.rule("eligible_age", |r| r.when(field("user.age").gte(18_i64)))
.rule("active_account", |r| r.when(field("user.status").eq("active")))
.rule("can_proceed", |r| {
r.when(rule_ref("eligible_age").and(rule_ref("active_account")))
})
.terminal("can_proceed", 0)
.compile()
.expect("failed to compile ruleset");
let ctx = Context::new()
.set("user.age", 25_i64)
.set("user.status", "active");
match ruleset.evaluate(&ctx) {
Some(verdict) => println!("Result: {verdict}"),
None => println!("No terminal matched."),
}
Core Concepts
Rules
A rule is a named boolean predicate over a context. Rules can reference input fields directly or depend on the output of other rules (chaining).
Terminals
Terminal rules are the final outputs of evaluation. Each terminal has a priority (lower number = higher priority). Evaluation short-circuits at the first terminal that resolves to true, enabling deny-before-allow patterns:
use ooroo::{RuleSetBuilder, field};
let ruleset = RuleSetBuilder::new()
.rule("banned", |r| r.when(field("user.banned").eq(true)))
.rule("eligible", |r| r.when(field("user.age").gte(18_i64)))
.terminal("banned", 0) // checked first
.terminal("eligible", 10) // only if no deny
.compile()
.unwrap();
Context
The context is the runtime input data. It supports dot-notation for nested field access (user.profile.age).
Performance
For maximum throughput, use IndexedContext which resolves field paths to integer indices at construction time:
use ooroo::{RuleSetBuilder, field};
let ruleset = RuleSetBuilder::new()
.rule("r", |r| r.when(field("score").gte(90_i64)))
.terminal("r", 0)
.compile()
.unwrap();
let ctx = ruleset.context_builder()
.set("score", 95_i64)
.build();
let result = ruleset.evaluate_indexed(&ctx);
Benchmark Results
On a typical machine (single-threaded, indexed context):
| Ruleset Size | Evaluation Time |
|---|---|
| 5 rules | ~72 ns |
| 20 rules | ~231 ns |
| 50 rules | ~630 ns |
Multi-threaded throughput scales linearly with thread count (zero contention).
Detailed Evaluation
When you need more than a boolean result:
use ooroo::{RuleSetBuilder, Context, field};
let ruleset = RuleSetBuilder::new()
.rule("r", |r| r.when(field("x").eq(1_i64)))
.terminal("r", 0)
.compile()
.unwrap();
let ctx = Context::new().set("x", 1_i64);
let report = ruleset.evaluate_detailed(&ctx);
println!("{report}");
println!("Evaluated to true: {:?}", report.evaluated());
println!("Duration: {:?}", report.duration());
Rule DSL
Rules can be defined in a text-based DSL instead of the builder API. This is useful for configuration files and non-engineer rule authoring.
Syntax
rule eligible_age:
user.age >= 18
rule active_account:
user.status == "active"
rule can_proceed (priority 10):
eligible_age AND active_account
rule hard_deny (priority 0):
user.banned == true
rule name:defines a regular rulerule name (priority N):defines a terminal rule with the given priority- Expressions: field comparisons (
==,!=,>,>=,<,<=), logical operators (AND,OR,NOT), parentheses, and rule references - Values: integers, floats, booleans (
true/false), strings ("quoted") - Comments:
#to end of line
Loading from a String
use ooroo::{RuleSet, Context};
let dsl = r#"
rule age_ok:
user.age >= 18
rule allowed (priority 0):
age_ok
"#;
let ruleset = RuleSet::from_dsl(dsl).expect("failed to parse rules");
let ctx = Context::new().set("user.age", 25_i64);
let verdict = ruleset.evaluate(&ctx);
Loading from a File
use ooroo::RuleSet;
let ruleset = RuleSet::from_file("rules.ooroo").expect("failed to load rules");
Examples
See the examples/ directory:
basic.rs-- Minimal ruleset compilation and evaluationpriority.rs-- Deny-before-allow pattern with terminal prioritiesdetailed_report.rs-- Usingevaluate_detailed()for diagnosticsmultithreaded.rs-- Sharing aRuleSetacross threads viaArcdsl.rs-- Loading rules from a.oorooDSL file
Run an example with:
cargo run --example basic
License
MIT
Dependencies
~1–2MB
~49K SLoC