pub struct Engine { /* private fields */ }Expand description
JSONLogic compile/evaluate engine.
Holds the immutable engine state — registered crate::CustomOperator
implementations, the EvaluationConfig, the optional
preserve-structure flag — and exposes the public surface for parsing
rules (Self::compile), evaluating them (Self::eval /
Self::eval_str, Self::eval_into), and opening hot-loop
sessions (Self::session).
Enabling the trace feature also exposes Self::trace for traced sessions.
Engine is Send + Sync (every field is); the typical pattern is to
build one at startup, wrap it in Arc<Engine>, and clone the Arc
across threads or async tasks.
§Example
use datalogic_rs::Engine;
// 1. Build the engine.
let engine = Engine::new();
// 2. Compile a rule once.
let logic = engine
.compile(r#"{"if": [{">=": [{"var": "age"}, 18]}, "adult", "minor"]}"#)
.unwrap();
// 3. Evaluate against many inputs (here via `Session::eval_str`;
// drop to `Engine::evaluate` if you want zero-copy borrowed results).
let mut session = engine.session();
for age in [12, 18, 42] {
let payload = format!(r#"{{"age": {age}}}"#);
let result = session.eval_str(&logic, &payload).unwrap();
assert!(result == "\"adult\"" || result == "\"minor\"");
session.reset();
}§Choosing an evaluate method
Start here. Use Self::eval_str for one-shot calls. Switch
to crate::Session once you’re evaluating the same compiled rule
many times — it reuses one arena instead of allocating per call.
Drop down to Self::evaluate only when you’re managing your own
bumpalo::Bump (custom pools, integration with arena-aware
downstream code).
Result-shape suffixes work the same on every tier: (none) returns
datavalue::OwnedDataValue, _str returns String (JSON),
_into::<T> returns T: DeserializeOwned (requires serde_json).
The raw Self::evaluate is the only method that exposes
&'a DataValue<'a> and a caller-owned &Bump.
Three tiers, in order of caller control:
| Method | Arena ownership | Result type | When to use |
|---|---|---|---|
Self::eval / Self::eval_str / Self::eval_into | engine creates a fresh Bump::with_capacity(4096) per call | OwnedDataValue / String / T | One-shot. Any caller that doesn’t want to think about arenas. Allocates each call — for hot loops, drop to Session. |
crate::Session::eval / crate::Session::eval_str / crate::Session::eval_into / crate::Session::eval_borrowed | session-owned Bump, caller calls crate::Session::reset between batches | owned / String / T / borrowed &'a DataValue<'a> | Hot loop with a long-lived engine. The Session hides bumpalo from the call site and pre-sizes the arena via crate::Session::reset_with_capacity when needed. |
Self::evaluate | caller-passed &Bump; library never resets | &'a DataValue<'a> (borrowed) | Zero-copy result paths, custom pool/allocator strategies, integration with arena-aware downstream code. |
All routes share the same dispatcher; the differences are who owns the arena, what the result type looks like, and whether the boundary parses / serialises JSON for you. There is no perf difference between the arena-aware paths once the bump is warm.
See the crate-level docs for the two-phase architecture, threading
model, and walk-through examples; see the Session and
EvaluationConfig rustdoc for arena-management and behaviour-tuning
options respectively.
Implementations§
Source§impl Engine
impl Engine
Sourcepub fn builder() -> EngineBuilder
pub fn builder() -> EngineBuilder
Start a crate::EngineBuilder for fluent construction.
Use the builder when you need a non-default EvaluationConfig,
templating mode, or pre-registered custom operators.
For a stock engine, Self::new is shorter.
Sourcepub fn session(&self) -> Session<'_>
pub fn session(&self) -> Session<'_>
Open a crate::Session handle that owns a reusable arena and
returns owned results, so callers don’t need to manage a
bumpalo::Bump themselves.
Use this when you want the throughput of arena reuse without the
lifetime juggling of Self::evaluate. The arena resets at the start
of every eval* call; results are deep-cloned out of the arena
before returning, so they outlive the next reset.
§Example
use datalogic_rs::Engine;
let engine = Engine::new();
let compiled = engine.compile(r#"{"+": [{"var": "x"}, 1]}"#).unwrap();
let mut session = engine.session();
let result = session.eval_str(&compiled, r#"{"x": 41}"#).unwrap();
assert_eq!(result, "42");Sourcepub fn new() -> Self
pub fn new() -> Self
Creates a new Engine engine with all built-in operators.
The engine includes 50+ built-in operators optimized with OpCode dispatch.
Templating mode is disabled by default. For non-default
configuration (custom EvaluationConfig, templating mode,
pre-registered custom operators) prefer Self::builder.
§Example
use datalogic_rs::Engine;
let engine = Engine::new();Sourcepub fn config(&self) -> &EvaluationConfig
pub fn config(&self) -> &EvaluationConfig
Gets a reference to the current evaluation configuration.
Sourcepub fn has_custom_operator(&self, name: &str) -> bool
pub fn has_custom_operator(&self, name: &str) -> bool
Checks if a custom operator with the given name is registered.
Operator registration is builder-only; this is a read-only check
against the frozen set produced by crate::EngineBuilder.
Sourcepub fn custom_operator_names(&self) -> impl Iterator<Item = &str>
pub fn custom_operator_names(&self) -> impl Iterator<Item = &str>
Iterator over the names of every custom operator registered on this engine (built-ins are not included). Order is unspecified (HashMap iteration order). Useful for tooling, UIs, and tests that need to introspect what’s available.
Sourcepub fn compile<R: IntoLogic>(&self, rule: R) -> Result<Logic>
pub fn compile<R: IntoLogic>(&self, rule: R) -> Result<Logic>
Compile a rule source into reusable Logic.
rule accepts any crate::IntoLogic shape: &str (JSON-parsed),
&OwnedDataValue / OwnedDataValue (cloned/moved), or
&serde_json::Value (gated on serde_json). For cross-thread
sharing prefer Self::compile_arc.
§Example
use datalogic_rs::Engine;
let engine = Engine::new();
let compiled = engine.compile(r#"{"==": [{"var": "x"}, 1]}"#).unwrap();Sourcepub fn compile_arc<R: IntoLogic>(&self, rule: R) -> Result<Arc<Logic>>
pub fn compile_arc<R: IntoLogic>(&self, rule: R) -> Result<Arc<Logic>>
Compile and wrap in an Arc in one call. Convenience for the
dominant cross-thread-sharing pattern; equivalent to
Arc::new(engine.compile(rule)?).
Sourcepub fn trace(&self) -> TracedSession<'_>
Available on crate feature trace only.
pub fn trace(&self) -> TracedSession<'_>
trace only.Open a crate::TracedSession over this engine. Calls made through
the session collect a per-call trace; the bare eval* methods on
Engine itself pay no trace overhead.
Available only when the crate is built with feature = "trace".
§Trace coverage
The session’s one-shot crate::TracedSession::eval_str compiles
the rule internally with optimization disabled, so every operator
in the rule surfaces a trace step.
The pre-compiled paths (crate::TracedSession::eval taking a
&Logic) inherit whatever shape that Logic was compiled into —
constant sub-expressions folded by Self::compile won’t appear,
since there is no operator left to execute. Use eval_str for
full coverage on a one-shot run.
§Example
use datalogic_rs::Engine;
let engine = Engine::new();
let run = engine
.trace()
.eval_str(r#"{"+": [1, 2]}"#, "null");
assert_eq!(run.result.unwrap(), "3");
// run.steps is the per-node execution log;
// run.expression_tree is the rule's compile-time tree shape.Sourcepub fn evaluate<'a, D: EvalInput<'a>>(
&self,
compiled: &'a Logic,
data: D,
arena: &'a Bump,
) -> Result<&'a DataValue<'a>>
pub fn evaluate<'a, D: EvalInput<'a>>( &self, compiled: &'a Logic, data: D, arena: &'a Bump, ) -> Result<&'a DataValue<'a>>
Evaluate compiled logic against arena-resident data — raw tier.
The caller owns the bumpalo::Bump lifecycle and may reset()
it between calls; the returned &DataValue<'a> borrows from the
arena, so it must be dropped before the next reset (enforced by
the borrow checker). For ergonomic owned/typed/JSON-string output,
prefer Self::eval / Self::eval_str
/ Self::eval_into.
§Example
use bumpalo::Bump;
use datalogic_rs::{Engine, DataValue};
let engine = Engine::new();
let compiled = engine.compile(r#"{"+": [{"var": "x"}, 2]}"#).unwrap();
let arena = Bump::new();
let data = DataValue::from_str(r#"{"x": 40}"#, &arena).unwrap();
let result = engine.evaluate(&compiled, data, &arena).unwrap();
assert_eq!(result.as_i64(), Some(42));data accepts any input shape understood by crate::EvalInput:
&'a DataValue<'a> (zero-cost passthrough), DataValue<'a>
(single arena alloc), &str (JSON-parsed), &OwnedDataValue
(deep-borrowed), or &serde_json::Value (gated on serde_json).
Sourcepub fn eval<R, D>(&self, rule: R, data: D) -> Result<OwnedDataValue>where
R: IntoLogic,
D: OwnedInput,
pub fn eval<R, D>(&self, rule: R, data: D) -> Result<OwnedDataValue>where
R: IntoLogic,
D: OwnedInput,
One-shot evaluation returning datavalue::OwnedDataValue.
Compiles rule, parses data, evaluates against a fresh
per-call arena, and deep-clones the result out. For the same
rule run repeatedly, escalate to Self::compile + a
Session.
§Example
use datalogic_rs::Engine;
let engine = Engine::new();
let result = engine.eval(
r#"{"+": [{"var": "x"}, 1]}"#,
r#"{"x": 41}"#,
).unwrap();
assert_eq!(result.as_i64(), Some(42));Sourcepub fn eval_str<R, D>(&self, rule: R, data: D) -> Result<String>where
R: IntoLogic,
D: OwnedInput,
pub fn eval_str<R, D>(&self, rule: R, data: D) -> Result<String>where
R: IntoLogic,
D: OwnedInput,
Sourcepub fn eval_into<T, R, D>(&self, rule: R, data: D) -> Result<T>
Available on crate feature serde_json only.
pub fn eval_into<T, R, D>(&self, rule: R, data: D) -> Result<T>
serde_json only.One-shot evaluation deserialised into a typed T: DeserializeOwned.
Use T = serde_json::Value for a JSON Value result; use a typed
struct for direct mapping. Internally routes through serde_json
(round-trips the result through a JSON value).
§Example
use datalogic_rs::Engine;
use serde_json::Value;
let engine = Engine::new();
let result: Value = engine.eval_into(
r#"{"+": [{"var": "x"}, 1]}"#,
r#"{"x": 41}"#,
).unwrap();
assert_eq!(result, Value::from(42));