- CEL-CXX: CEL Rust library
CEL-CXX: CEL Rust library
A type-safe Rust library for Common Expression Language (CEL), built on top of cel-cpp with zero-cost FFI bindings via cxx.
Architecture Overview
Core Design Principles
- Type Safety: Compile-time verification of CEL expressions and function signatures
- Zero-Cost Abstractions: Direct FFI calls to CEL-CPP with minimal overhead
- Memory Safety: Rust ownership system prevents common integration bugs
- Ergonomic API: Builder patterns and automatic type inference reduce boilerplate
- Extensibility: Support for custom types and async operations
Integration Architecture
The library provides a layered architecture that bridges Rust and CEL-CPP:
- Application Layer: High-level APIs for environment building and expression evaluation
- Type System Layer: Automatic conversions between Rust and CEL types
- FFI Layer: Zero-cost bindings to CEL-CPP via the
cxx
crate - CEL-CPP Layer: Google's reference implementation for parsing and evaluation
Quick Start
Installation
Add to your Cargo.toml
:
[]
= "0.2.1"
# Optional features
= { = "0.2.1", = ["tokio"] }
Basic Expression Evaluation
use *;
// 1. Build environment with variables and functions
let env = builder
.?
.?
.register_global_function?
.build?;
// 2. Compile expression
let program = env.compile?;
// 3. Create activation with variable bindings
let activation = new
.bind_variable?
.bind_variable?;
// 4. Evaluate
let result = program.evaluate?;
println!; // "Hello Alice! You are an adult"
# Ok::
Custom Types with Derive Macros
use *;
// Specify type name in CEL type system.
// Generates `std::fmt::Display` impl for User` with `Debug` trait.
// or you can specify a custom format.
// Generates `std::fmt::Display` impl with custom format.
let env = builder
.?
// ✨ Register struct methods directly - &self becomes CEL receiver
.register_member_function?
.register_member_function?
.register_member_function?
.build?;
let program = env.compile?;
# Ok::
Zero-Annotation Function Registration
The library's flagship feature uses Generic Associated Types (GATs) to automatically infer function signatures, eliminating the need for manual type annotations:
use *;
let env = builder
// ✨ Function signatures automatically inferred from Rust types!
.register_global_function?
.register_global_function?
.register_global_function?
.register_global_function? // Result<i64, _> auto-handled
.build?;
# Ok::
This system supports a wide variety of function patterns:
Owned Type Parameters
Functions can accept owned values, which are automatically converted from CEL types:
use *;
let env = builder
// Basic owned types
.register_global_function?
.register_global_function?
.register_global_function?
// Complex owned types
.register_global_function?
.register_global_function?
.build?;
# Ok::
Reference Type Parameters
Reference parameters enable zero-copy operations for performance-critical code:
use *;
let env = builder
// String references - no copying required
.register_global_function?
.register_global_function?
// Collection element references - containers hold owned values
.register_global_function?
.register_global_function?
.build?;
# Ok::
Reference Type Return Values
Functions can return references to data within their parameters, enabling efficient data access:
use *;
// Define functions that return references with proper lifetime annotations
let env = builder
// Return string slices from borrowed parameters using named functions
.register_global_function?
// Return owned values from owned containers using closures
.register_global_function?
// Return references to parameter data using named functions
.register_global_function?
.build?;
# Ok::
Direct Return Values vs Result Types
The system supports both direct return values and Result<T, E>
for error handling:
use *;
use ParseIntError;
use io;
let env = builder
// Direct return values - always succeed
.register_global_function?
.register_global_function?
// Result return values - can fail gracefully with standard library errors
.register_global_function?
.register_global_function?
// Result with owned return values and concrete error types
.register_global_function?
.build?;
# Ok::
Synchronous vs Asynchronous Functions
Both sync and async functions are supported seamlessly:
#
# async
Function Signature Examples
Here's a comprehensive overview of supported function signatures:
use *;
// Define function that returns reference with proper lifetime annotation
// All of these function signatures are automatically inferred:
let env = builder
// No parameters
.register_global_function?
.register_global_function?
// Single parameter - various types
.register_global_function?
.register_global_function?
.register_global_function?
// Multiple parameters - mixed types (using named function for lifetime)
.register_global_function?
// Generic collections - owned containers
.register_global_function?
.register_global_function?
// Optional types
.register_global_function?
// Result types for error handling with standard library errors
.register_global_function?
.register_global_function?
.build?;
# Ok::
Advanced Features
Async Support
When the async
feature is enabled, you can evaluate expressions asynchronously:
#
# async
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:
-
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. -
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
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()
, 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:
use *;
let env = builder
// Multiple functions with same name, different signatures
.register_global_function?
.register_global_function?
.register_global_function?
.build?;
// CEL will automatically choose the right overload based on argument types
let program1 = env.compile?; // Calls i64 version
let program2 = env.compile?; // Calls f64 version
let program3 = env.compile?; // Calls String version
# Ok::
Smart Reference Handling
The library automatically manages reference types with safe lifetime handling:
use *;
use HashMap;
// ✅ These reference patterns work automatically:
let env = builder
.? // Borrowed strings
.? // Borrowed values
.? // Optional borrows
.build?;
// The library prevents unsafe patterns at compile time:
// ❌ .declare_variable::<&Vec<String>>("invalid")? // Compiler error
# Ok::
Type System
The crate provides comprehensive type support with automatic conversions between CEL and Rust types. All types support the three core traits for seamless integration:
CEL Type | Rust Type | |||
---|---|---|---|---|
Declare | To CEL | From CEL | ||
TypedValue |
IntoValue |
FromValue |
||
null |
() |
✅ | ✅ | |
bool |
bool |
✅ | ✅ | |
int |
i64 , i32 , i16 , isize |
✅ | ✅ | |
uint |
u64 , u32 , u16 , usize |
✅ | ✅ | |
double |
f64 , f32 |
✅ | ✅ | |
string |
String , ArcStr , Box<str> , str |
✅ | ✅ | |
bytes |
Vec<u8> , ArcBytes , Box<[u8]> , [u8] |
✅ | ✅ | |
duration |
chrono::Duration |
✅ | ✅ | |
timestamp |
chrono::DateTime<Utc> , SystemTime |
✅ | ✅ | |
list<T> |
Vec<T> , VecDeque<T> , LinkedList<T> , [T] |
✅ | ✅ | |
map<K,V> |
HashMap<K,V> , BTreeMap<K,V> , Vec<(K,V)> |
✅ | ✅ | |
optional<T> |
Option<T> , Optional<T> |
✅ | ✅ | |
type |
ValueType |
✅ | ✅ | |
error |
Error |
✅ | ✅ | |
opaque |
#[derive(Opaque)] struct |
✅ | ✅ |
Special Reference Support: All &T
types support Declare and To CEL operations,
enabling zero-copy function arguments like &str
, &[u8]
, &MyStruct
, etc.
Type Conversion Examples
use *;
use VecDeque;
// Automatic conversions work seamlessly
let env = builder
// Different integer types all map to CEL int
.register_global_function?
.register_global_function?
// String types are interchangeable
.register_global_function?
.register_global_function?
// Container types work with any compatible Rust collection
.register_global_function?
.register_global_function?
.build?;
# Ok::
Feature Flags
Feature | Description | Default |
---|---|---|
derive |
Derive macros for custom types (#[derive(Opaque)] ) |
✅ |
async |
Async/await support for expressions and functions | ❌ |
tokio |
Tokio async runtime integration (requires async ) |
❌ |
smol |
smol runtime integration (requires async ) |
❌ |
async-std |
async-std runtime integration (requires async ) |
❌ |
Performance Characteristics
- Zero-cost FFI: Direct C++ function calls with no marshaling overhead
- Compile-time optimization: Function signatures resolved at compile time
- Memory efficient: Minimal allocations through smart reference handling
- Async overhead: Only when async features are explicitly used
- Type safety: Compile-time prevention of common integration errors
Examples
The crate includes comprehensive examples demonstrating various features:
- Basic Usage: Variable binding, function registration, expression evaluation
- Custom Types: Derive macros, member functions, type integration
- Async Support: Tokio/smol/async-std integration examples
- Advanced Features: Function overloads, error handling, complex type conversions
Run examples with:
Platform Support
Legend:
- ✅ Tested: Confirmed working with automated tests
- 🟡 Should work: Build configuration exists but not tested in CI
Cross-Compilation Support
cel-cxx includes built-in support for cross-compilation via cross-rs. The build system automatically detects cross-compilation environments and configures the appropriate toolchains.
Usage with cross-rs:
# Install cross-rs
# Build for aarch64
Note: Not all cross-rs targets are supported due to CEL-CPP's build requirements. musl targets and some embedded targets may not work due to missing C++ standard library support or incompatible toolchains.
Android Build Instructions
Android builds require additional setup beyond the standard Rust toolchain:
Prerequisites:
- Install Android NDK and set
ANDROID_NDK_HOME
- Install
cargo-ndk
for simplified Android builds
# Install cargo-ndk
# Add Android targets
Building for Android:
# Build for ARM64 (recommended)
# Build for ARMv7
# Build for x86_64 (emulator)
# Build for i686 (emulator)
Why cargo-ndk is required:
ANDROID_NDK_HOME
configures Bazel for CEL-CPP compilationcargo-ndk
automatically sets upCC_{target}
andAR_{target}
environment variables needed for the Rust FFI layer- This ensures both the C++ (CEL-CPP) and Rust (cel-cxx-ffi) components use compatible toolchains
CEL Feature Support
Core Language Features
Feature | Status | Description |
---|---|---|
Basic Types | ✅ | null , bool , int , uint , double , string , bytes |
Collections | ✅ | list<T> , map<K,V> with full indexing and comprehensions |
Time Types | ✅ | duration , timestamp with full arithmetic support |
Operators | ✅ | Arithmetic, logical, comparison, and membership operators |
Variables | ✅ | Variable binding and scoping |
Conditionals | ✅ | Ternary operator and logical short-circuiting |
Comprehensions | ✅ | List and map comprehensions with filtering |
Custom Types | ✅ | Opaque types via #[derive(Opaque)] |
Macros | ✅ | CEL macro expansion support |
Function Overloads | ✅ | Multiple function signatures with automatic resolution |
Type Checking | ✅ | Compile-time type validation |
Standard Library
Feature | Status | Description |
---|---|---|
Built-in Functions | ✅ | Core CEL functions: size() , type() , has() , etc. |
String Functions | ✅ | contains() , startsWith() , endsWith() , matches() |
List Functions | ✅ | all() , exists() , exists_one() , filter() , map() |
Map Functions | ✅ | Key/value iteration and manipulation |
Type Conversion | ✅ | int() , double() , string() , bytes() , duration() , timestamp() |
Math Functions | ✅ | Basic arithmetic and comparison operations |
Optional Value Support
Feature | Status | Description |
---|---|---|
Optional Types | ✅ | optional<T> with safe navigation and null handling |
Safe Navigation | ✅ | ?. operator for safe member access |
Optional Chaining | ✅ | Chain optional operations without explicit null checks |
Value Extraction | ✅ | value() and hasValue() functions for optional handling |
Optional Macros | ✅ | optional.of() , optional.ofNonZeroValue() macros |
Extension Libraries
Extension | Status | Description |
---|---|---|
Strings Extension | ✅ | Advanced string operations: split() , join() , replace() , format() |
Math Extension | ✅ | Mathematical functions: math.greatest() , math.least() , math.abs() , math.sqrt() , bitwise ops |
Lists Extension | ✅ | Enhanced list operations: flatten() , reverse() , slice() , unique() |
Sets Extension | ✅ | Set operations: sets.contains() , sets.equivalent() , sets.intersects() |
Regex Extension | ✅ | Regular expression support: matches() , findAll() , split() |
Encoders Extension | ✅ | Encoding/decoding: base64.encode() , base64.decode() , URL encoding |
Bindings Extension | ✅ | Variable binding and scoping enhancements |
Runtime Features
Feature | Status | Description |
---|---|---|
Custom Functions | ✅ | Register custom Rust functions with automatic type conversion |
Async Support | ✅ | Async function calls and evaluation with Tokio integration |
Custom Extensions | ✅ | Build and register custom CEL extensions |
Performance Optimization | ✅ | Optimized evaluation with caching and short-circuiting |
Planned Features
Feature | Status | Description |
---|---|---|
Protocol Buffer Integration | 🚧 Planned | Direct support for protobuf messages and enums as native CEL types |
Prerequisites
System Requirements
- Rust: 1.80+
- C++ Toolchain: C++17 compatible compiler
- Linux: GCC 9+ or Clang 15+
- macOS: Xcode 10+ or Clang 15+
- Windows: MSVC 2022+
Installation Verification
# Clone and test
# Run examples
License
Licensed under the Apache License 2.0. See LICENSE for details.
Acknowledgements
google/cel-cpp
- The foundational C++ CEL implementationdtolnay/cxx
- Safe and efficient Rust-C++ interoprmanoka/async-scoped
- Scoped async execution for safe lifetime management- The CEL community and other Rust CEL implementations for inspiration and ecosystem growth