Skip to main content

Crate cel_cxx

Crate cel_cxx 

Source
Expand description

github crates-io docs-rs deepwiki

§CEL-CXX

A type-safe Rust library for Common Expression Language (CEL), built on top of cel-cpp with zero-cost FFI bindings via cxx.

§Documentation

For detailed guides on architecture, function registration, type system, and advanced features, see the documentation directory.

§Quick Start

§Installation

Add to your Cargo.toml:

[dependencies]
cel-cxx = "0.2.4"

# Optional features
cel-cxx = { version = "0.2.4", features = ["tokio"] }

# Protobuf derive macros (choose your backend)
cel-cxx = { version = "0.2.4", features = ["prost"] }
cel-cxx = { version = "0.2.4", features = ["protobuf-legacy"] }

§Basic Expression Evaluation

use cel_cxx::*;

// 1. Build environment with variables and functions
let env = Env::builder()
    .declare_variable::<String>("name")?
    .declare_variable::<i64>("age")?
    .register_global_function("adult", |age: i64| age >= 18)?
    .build()?;

// 2. Compile expression
let program = env.compile("'Hello ' + name + '! You are ' + (adult(age) ? 'an adult' : 'a minor')")?;

// 3. Create activation with variable bindings
let activation = Activation::new()
    .bind_variable("name", "Alice")?
    .bind_variable("age", 25i64)?;

// 4. Evaluate
let result = program.evaluate(&activation)?;
println!("{}", result); // "Hello Alice! You are an adult"

§Custom Types with Derive Macros

use cel_cxx::*;

#[derive(Opaque, Debug, Clone, PartialEq)]
// Specify type name in CEL type system.
#[cel_cxx(type = "myapp.User")]
// Generates `std::fmt::Display` impl for User` with `Debug` trait.
#[cel_cxx(display)]
// or you can specify a custom format.
// Generates `std::fmt::Display` impl with custom format.
#[cel_cxx(display = write!(fmt, "User(name={name})", name = &self.name))]
struct User {
    name: String,
    age: i32,
    roles: Vec<String>,
}

impl User {
    // Struct methods can be registered directly as CEL member functions
    fn has_role(&self, role: &str) -> bool {
        self.roles.contains(&role.to_string())
    }
    
    fn is_adult(&self) -> bool {
        self.age >= 18
    }
    
    fn get_role_count(&self) -> i64 {
        self.roles.len() as i64
    }
}

let env = Env::builder()
    .declare_variable::<User>("user")?
    // ✨ Register struct methods directly - &self becomes CEL receiver
    .register_member_function("has_role", User::has_role)?
    .register_member_function("is_adult", User::is_adult)?
    .register_member_function("get_role_count", User::get_role_count)?
    .build()?;

let program = env.compile("user.has_role('admin') && user.is_adult()")?;

§Platform Support

Platform Target Triple Status Notes
Linux x86_64-unknown-linux-gnu Tested
aarch64-unknown-linux-gnu Tested
armv7-unknown-linux-gnueabi Tested via cross-rs
i686-unknown-linux-gnu Tested via cross-rs
Windows x86_64-pc-windows-msvc Tested (Visual Studio 2022+)
macOS x86_64-apple-darwin Tested
aarch64-apple-darwin Tested
arm64e-apple-darwin Tested
Android aarch64-linux-android 🟡 Should work, use cargo-ndk
armv7-linux-androideabi 🟡 Should work, use cargo-ndk
x86_64-linux-android 🟡 Should work, use cargo-ndk
i686-linux-android 🟡 Should work, use cargo-ndk
iOS aarch64-apple-ios 🟡 Should work, untested
aarch64-apple-ios-sim 🟡 Should work, untested
x86_64-apple-ios 🟡 Should work, untested
arm64e-apple-ios 🟡 Should work, untested
tvOS aarch64-apple-tvos 🟡 Should work, untested
aarch64-apple-tvos-sim 🟡 Should work, untested
x86_64-apple-tvos 🟡 Should work, untested
watchOS aarch64-apple-watchos 🟡 Should work, untested
aarch64-apple-watchos-sim 🟡 Should work, untested
x86_64-apple-watchos-sim 🟡 Should work, untested
arm64_32-apple-watchos 🟡 Should work, untested
armv7k-apple-watchos 🟡 Should work, untested
visionOS aarch64-apple-visionos 🟡 Should work, untested
aarch64-apple-visionos-sim 🟡 Should work, untested
WebAssembly wasm32-unknown-emscripten Tested via cross-rs

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
cargo install cross --git https://github.com/cross-rs/cross

# Build for aarch64
cross build --target aarch64-unknown-linux-gnu

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:

  1. Install Android NDK and set ANDROID_NDK_HOME
  2. Install cargo-ndk for simplified Android builds
# Install cargo-ndk
cargo install cargo-ndk

# Add Android targets
rustup target add aarch64-linux-android
rustup target add armv7-linux-androideabi
rustup target add x86_64-linux-android
rustup target add i686-linux-android

Building for Android:

# Build for ARM64 (recommended)
cargo ndk --target aarch64-linux-android build

# Build for ARMv7
cargo ndk --target armv7-linux-androideabi build

# Build for x86_64 (emulator)
cargo ndk --target x86_64-linux-android build

# Build for i686 (emulator)
cargo ndk --target i686-linux-android build

Why cargo-ndk is required:

  • ANDROID_NDK_HOME configures Bazel for CEL-CPP compilation
  • cargo-ndk automatically sets up CC_{target} and AR_{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

FeatureStatusDescription
Basic Typesnull, bool, int, uint, double, string, bytes
Collectionslist<T>, map<K,V> with full indexing and comprehensions
Time Typesduration, timestamp with full arithmetic support
OperatorsArithmetic, logical, comparison, and membership operators
VariablesVariable binding and scoping
ConditionalsTernary operator and logical short-circuiting
ComprehensionsList and map comprehensions with filtering
Custom TypesOpaque types via #[derive(Opaque)]
Protobuf Message TypeNative protobuf messages and enums as first-class CEL types with field access, message construction, and round-trip serialization
MacrosCEL macro expansion support
Function OverloadsMultiple function signatures with automatic resolution
Type CheckingCompile-time type validation

§Standard Library

FeatureStatusDescription
Built-in FunctionsCore CEL functions: size(), type(), has(), etc.
String Functionscontains(), startsWith(), endsWith(), matches()
List Functionsall(), exists(), exists_one(), filter(), map()
Map FunctionsKey/value iteration and manipulation
Type Conversionint(), double(), string(), bytes(), duration(), timestamp()
Math FunctionsBasic arithmetic and comparison operations

§Optional Value Support

FeatureStatusDescription
Optional Typesoptional<T> with safe navigation and null handling
Safe Navigation?. operator for safe member access
Optional ChainingChain optional operations without explicit null checks
Value Extractionvalue() and hasValue() functions for optional handling
Optional Macrosoptional.of(), optional.ofNonZeroValue() macros

§Extension Libraries

ExtensionStatusDescription
Strings ExtensionAdvanced string operations: split(), join(), replace(), format()
Math ExtensionMathematical functions: math.greatest(), math.least(), math.abs(), math.sqrt(), bitwise ops
Lists ExtensionEnhanced list operations: flatten(), reverse(), slice(), unique()
Sets ExtensionSet operations: sets.contains(), sets.equivalent(), sets.intersects()
Regex ExtensionRegular expression support: matches(), findAll(), split()
Encoders ExtensionEncoding/decoding: base64.encode(), base64.decode(), URL encoding
Bindings ExtensionVariable binding and scoping enhancements

§Runtime Features

FeatureStatusDescription
Custom FunctionsRegister custom Rust functions with automatic type conversion
Async SupportAsync function calls and evaluation with Tokio integration
Custom ExtensionsBuild and register custom CEL extensions
Performance OptimizationOptimized evaluation with caching and short-circuiting

§Protobuf Integration

cel-cxx supports native Protocol Buffer messages as first-class CEL types. You can bind serialized protobuf messages as variables, access their fields in CEL expressions, construct new messages, and extract results back to Rust.

§Compiling Proto Descriptors

CEL needs a FileDescriptorSet (a binary descriptor file) to understand your .proto types. The recommended approach is protox, a pure-Rust protobuf compiler that requires no external binary:

use prost::Message;

let fds = protox::compile(["proto/my_service.proto"], ["proto/"]).unwrap();
let descriptor_bytes = fds.encode_to_vec();

Alternatively, you can use protoc directly:

protoc --descriptor_set_out=descriptors.bin \
       --include_imports \
       --proto_path=proto \
       proto/my_service.proto

§Setting Up the Environment

use cel_cxx::*;

let env = Env::builder()
    .with_file_descriptor_set(&descriptor_bytes)
    .declare_variable_with_type("msg", ValueType::Struct(StructType::new("my.package.MyMessage")))?
    .build()?;

§Binding Protobuf Input

Serialize your message to bytes (e.g., via prost) and bind it:

let activation = Activation::new()
    .bind_variable_dynamic("msg", StructValue::from_bytes("my.package.MyMessage", serialized_bytes))?;

§Accessing Fields in CEL

CEL expressions can access all protobuf field types:

msg.name                              // scalar fields
msg.address.city                      // nested message fields
msg.tags[0]                           // repeated fields
msg.labels["env"]                     // map fields
msg.status == my.package.Status.ACTIVE // enum constants
has(msg.optional_field)               // field presence

§Message Construction

Construct new protobuf messages directly in CEL:

my.package.MyMessage{name: "Alice", id: 42}

§Extracting Results

// Borrow via as_struct()
let sv = result.as_struct().unwrap();
let type_name = sv.type_name();
let bytes = sv.to_bytes();

// Or extract an owned StructValue
let sv = StructValue::from_value(&result)?;

// Read individual fields from Rust
let name = env.get_struct_field(&sv, "name")?;
let has_name = env.has_struct_field(&sv, "name")?;

§Typed API with Derive Macros

If you have compile-time protobuf types (via prost-build or protobuf-codegen), derive macros let you skip the manual StructValue::from_bytes plumbing and use the standard typed API instead:

// With the `prost` or `protobuf-legacy` feature enabled:
let env = Env::builder()
    .with_file_descriptor_set(&descriptors)
    .declare_variable::<MyMessage>("msg")?  // instead of declare_variable_with_type(...)
    .build()?;

let activation = Activation::new()
    .bind_variable("msg", my_message)?;  // instead of bind_variable_dynamic(...)

let result = program.evaluate(&activation)?;
let recovered = MyMessage::from_value(&result)?;  // instead of StructValue::from_value(...)

The derives are injected during code generation in your build.rs – one line per type for prost, or a small CustomizeCallback for protobuf-codegen.

See the Protobuf Derive Macros Guide for full setup instructions, feature flags, and examples.

§Well-Known Type Handling

cel-cpp automatically converts well-known types to their CEL equivalents:

Protobuf TypeCEL TypeNotes
google.protobuf.DurationdurationFull arithmetic and comparison support
google.protobuf.TimestamptimestampFull arithmetic and comparison support
google.protobuf.Int64Value, StringValue, BoolValue, …PrimitivesAuto-unboxed to int, string, bool, etc.
google.protobuf.StructmapDynamic map with dot-notation access
google.protobuf.Anyhas() works; full unpacking depends on cel-cpp support

§Performance Note

Protobuf messages cross the Rust/C++ FFI boundary via serialization: messages are serialized to bytes on the Rust side and deserialized into arena-allocated C++ messages for evaluation, then serialized back when extracting results. This adds overhead proportional to message size.

Zero-copy is not currently implemented for two reasons. First, the architectural change to keep C++ arena-allocated messages alive across the FFI boundary would be significant. Second, true zero-copy would require both cel-cxx and the Rust protobuf library to link against the exact same C++ protobuf library instance so that message pointers can be passed directly across the boundary. The official Google Rust protobuf crate (protobuf) supports a C++ kernel backend that would make this possible in theory, but it currently requires Bazel (not cargo), and there is no shared protobuf-cpp-sys crate that both libraries could depend on. cel-cxx also compiles its own copy of C++ protobuf as part of cel-cpp via cmake, so integrating a shared dependency would require reworking the build. Until the ecosystem converges, serialization/deserialization is the only correct approach.

§License

Licensed under the Apache License 2.0. See LICENSE for details.

§Acknowledgements

  • google/cel-cpp - The foundational C++ CEL implementation
  • dtolnay/cxx - Safe and efficient Rust-C++ interop
  • rmanoka/async-scoped - Scoped async execution for safe lifetime management
  • The CEL community and other Rust CEL implementations for inspiration and ecosystem growth

Re-exports§

pub use async::AsyncStd;async-std
pub use async::Tokio;tokio
pub use async::Smol;smol
pub use marker::*;
pub use env::*;
pub use program::*;
pub use error::*;
pub use function::*;
pub use macros::*;
pub use variable::*;
pub use activation::*;
pub use kind::*;
pub use types::*;
pub use values::*;
pub use maybe_future::*;

Modules§

activation
Activation context for expression evaluation.
asyncasync
Asynchronous runtime support for CEL.
env
Environment for compiling CEL expressions.
error
Error types and error handling utilities.
function
Function registration and declaration utilities. Function registration and implementation utilities.
kind
CEL type system kinds and type classification.
macros
CEL macro system for compile-time expression expansion. CEL macro system for extending the expression language.
marker
Marker types and traits for function and runtime polymorphism.
maybe_future
Conditional future types for async/sync compatibility.
program
Compiled CEL programs ready for evaluation. Compiled CEL program evaluation.
types
CEL type system types and type definitions.
values
CEL Value Types and Operations
variable
Variable declarations for CEL environments. Variable declaration and binding utilities.

Derive Macros§

Opaquederive
Derive macro for creating opaque types for cxx-cel.
ProstValuederive
Derive macro for prost-generated protobuf types.
ProtobufLegacyValuederive
Derive macro for protobuf v3 (stepancheg) generated types.
ProtobufValuederive
Derive macro for protobuf v4 (cpp kernel) types — stub, not yet implemented.