cel-cxx 0.2.5

A high-performance, type-safe Rust interface for Common Expression Language (CEL), build on top of cel-cpp with zero-cost FFI bindings via cxx
Documentation
# Advanced Features


## Async Support


When the `async` feature is enabled, you can evaluate expressions asynchronously:

```rust,no_run
# #[cfg(feature = "async")]

# async fn example() -> Result<(), cel_cxx::Error> {

use cel_cxx::*;

let env = Env::builder()
    .use_tokio()
    .register_global_function("async_fetch", async |id: i64| -> Result<String, Error> {
        // Simulate async database call
        tokio::time::sleep(std::time::Duration::from_millis(100)).await;
        Ok(format!("Data for ID: {}", id))
    })?
    .build()?;

let program = env.compile("async_fetch(42)")?;
let result = program.evaluate(()).await?;
# Ok(())

# }

```

### Async Architecture Design


Supporting Rust async functions in CEL presents unique challenges since CEL-CPP doesn't
natively support asynchronous or callback-based user-defined functions and variable providers.
When a Rust async function returns a `Future`, it has already exited the current stack frame,
and the C++ CEL evaluation engine cannot schedule or await Rust futures.

**cel-cxx** solves this through an innovative dual-threading architecture:

1. **Async-to-Blocking Bridge**: When async functions or variable providers are registered,
   the entire program evaluation is moved to a blocking thread using `Runtime::spawn_blocking()`.
   The main async context receives a future that resolves when evaluation completes.

2. **Blocking-to-Async Bridge**: When async callbacks are invoked within the blocking thread,
   the returned futures are dispatched back to the async runtime for execution, while the
   blocking thread waits for completion using `Runtime::block_on()`.

### Implementation Details


- **Lifetime Management**: Since user-provided functions and variable providers can be capturing
  closures with complex lifetimes, cel-cxx uses the [`async-scoped`]https://crates.io/crates/async-scoped
  crate to safely manage these lifetimes across thread boundaries.

- **Multi-threaded Runtime Requirement**: When using Tokio, the runtime must be multi-threaded
  because the implementation relies on [`tokio::task::block_in_place()`]https://docs.rs/tokio/latest/tokio/task/fn.block_in_place.html,
  which panics in single-threaded runtimes.

This design enables seamless integration of async Rust code with the synchronous CEL-CPP
evaluation engine, maintaining both performance and correctness across runtime boundaries.

## Function Overloads


The library supports function overloading with automatic type resolution:

```rust,no_run
use cel_cxx::*;

let env = Env::builder()
    // Multiple functions with same name, different signatures
    .register_global_function("process", |x: i64| x * 2)?
    .register_global_function("process", |x: f64| x * 2.0)?
    .register_global_function("process", |x: String| x.to_uppercase())?
    .build()?;

// CEL will automatically choose the right overload based on argument types
let program1 = env.compile("process(42)")?;      // Calls i64 version
let program2 = env.compile("process(3.14)")?;    // Calls f64 version  
let program3 = env.compile("process('hello')")?; // Calls String version
# Ok::<(), cel_cxx::Error>(())

```

## Smart Reference Handling


The library automatically manages reference types with safe lifetime handling:

```rust,no_run
use cel_cxx::*;
use std::collections::HashMap;

// ✅ These reference patterns work automatically:
let env = Env::builder()
    .declare_variable::<Vec<&str>>("string_refs")?        // Borrowed strings
    .declare_variable::<HashMap<i64, &str>>("lookup")?    // Borrowed values
    .declare_variable::<Option<&str>>("maybe_str")?       // Optional borrows
    .build()?;

// The library prevents unsafe patterns at compile time:
// ❌ .declare_variable::<&Vec<String>>("invalid")?  // Compiler error
# Ok::<(), cel_cxx::Error>(())

```