cel-cxx: Modern Rust Interface for CEL
A high-performance, type-safe Rust interface for Common Expression Language (CEL), built on top of google/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.1.0"
# Optional features
= { = "0.1.0", = ["async", "derive", "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 *;
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 |
---|---|---|
async |
Async/await support for expressions and functions | ❌ |
derive |
Derive macros for custom types (#[derive(Opaque)] ) |
✅ |
tokio |
Tokio async 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 and async-std integration examples
- Advanced Features: Function overloads, error handling, complex type conversions
Run examples with:
🖥️ Platform Support
Platform | Status | Notes |
---|---|---|
Linux | ✅ Supported | Fully tested and supported |
macOS | ⚠️ Untested | Should work but not regularly tested |
Windows | ❌ Not Supported | CEL-CPP Bazel build scripts don't support Windows |
📋 CEL Feature Support
✅ Supported 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 |
Functions | ✅ | Built-in functions and custom function registration |
Variables | ✅ | Variable binding and scoping |
Conditionals | ✅ | Ternary operator and logical short-circuiting |
Comprehensions | ✅ | List and map comprehensions with filtering |
Optional Types | ✅ | optional<T> with safe navigation |
Custom Types | ✅ | Opaque types via #[derive(Opaque)] |
Extensions | ✅ | CEL language extensions and custom operators |
Macros | ✅ | CEL macro expansion support |
Async Support | ✅ | Async function calls and evaluation |
Function Overloads | ✅ | Multiple function signatures with automatic resolution |
Type Checking | ✅ | Compile-time type validation |
🚧 Planned Features
Feature | Status | Description |
---|---|---|
Protocol Buffer Integration | 🚧 Planned | Direct support for protobuf messages and enums as native CEL types |
Windows Support | 🚧 Planned | Requires CEL-CPP Windows build support |
📋 Prerequisites
System Requirements
- Rust: 1.70+ (for GATs support)
- C++ Toolchain: C++17 compatible compiler
- Linux: GCC 7+ or Clang 6+
- macOS: Xcode 10+ or Clang 6+
- Windows: MSVC 2019+ or Clang 6+
Installation Verification
# Clone and test
# Run examples
🤝 Contributing
We welcome contributions! Please see our Contributing Guide for details.
Development Setup
# Setup development environment
# Install dependencies
# Run tests
# Run examples
Code Style
- Follow standard Rust formatting (
cargo fmt
) - Ensure all tests pass (
cargo test
) - Add documentation for public APIs
- Include examples for new features
🔗 Related Crates
This project is part of a growing ecosystem of CEL implementations in Rust. Here's how it compares to other notable projects:
xjasonli/cel-cxx (This Project)
- Approach: FFI bindings to Google's official CEL-CPP implementation
- Performance: Zero-cost abstractions with direct C++ calls
- Type Safety: Compile-time verification via Rust's type system
- Features: Full CEL spec support, async/await, custom types via derive macros
- Best For: Production applications requiring full CEL compatibility and maximum performance
clarkmcc/cel-rust
- Approach: Native Rust interpreter with ANTLR-based parser
- Performance: Good performance for pure Rust, no C++ dependencies
- Features: Core CEL features, custom functions, concurrent execution
- Best For: Applications preferring pure Rust solutions or avoiding C++ dependencies
- Trade-offs: May not support all CEL spec features
1BADragon/rscel
- Approach: Native Rust implementation with custom parser
- Features: Basic CEL evaluation, custom types and functions
- Best For: Lightweight applications with simple CEL expression needs
- Trade-offs: More limited feature set compared to other implementations
thesayyn/cel-rs
- Approach: Native Rust with WebAssembly compilation target
- Features: Basic CEL features, WASM playground, browser compatibility
- Best For: Web applications and browser-based CEL evaluation
- Trade-offs: Focused on web use cases, may have limited server-side features
Choosing the Right Implementation
Requirement | Recommendation |
---|---|
Full CEL spec compliance | cel-cxx (this project) |
Pure Rust, no C++ deps | clarkmcc/cel-rust |
WebAssembly/Browser support | thesayyn/cel-rs |
Lightweight, simple use cases | 1BADragon/rscel |
Maximum performance | cel-cxx (this project) |
Async/await support | cel-cxx (this project) |
📄 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