news 2026/11/2 15:17:15

如何在cel-rust中实现自定义类型:面向高级开发者的Val trait完全指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
如何在cel-rust中实现自定义类型:面向高级开发者的Val trait完全指南

【免费下载链接】cel-rust

Common Expression Language interpreter written in Rust

项目地址:https://gitcode.com/gh_mirrors/ce/cel-rust
点击查看免费下载

cel-rust 是用 Rust 编写的 Common Expression Language(CEL)解释器,而Valtrait 正是它值系统的核心。本文将带你完整掌握如何在 cel-rust 中实现自定义类型:从最小可用的Val实现,到Env::add_type类型注册、as_*运算符能力、Indexer字段访问与错误处理,让你把自己的 Rust 结构体变成 CEL 表达式中可计算的值。

一、Val trait 是什么:cel-rust 值系统的入口 🧩

在 cel-rust 中,任何想在 CEL 表达式里"被求值"的 Rust 类型,都必须实现Valtrait。可以把它理解为一套"能力声明协议":

  • 身份:get_type()/cel_type()告诉解释器这个值属于哪个 CEL 类型;
  • 拷贝:clone_as_boxed()让解释器能把引用变成独立拥有的值;
  • 相等:equals()决定==/!=的结果(默认返回false,即任何值都不等于自身,务必实现);
  • 能力:一组as_adder()、as_comparer()、as_indexer()等访问器,默认返回None。

关键设计:解释器不关心你的类型是什么,它只"询问"值的能力。表达式a + b会先调用a.as_adder(),若返回Some(self),再把b交给Adder::add去计算;若返回None,表达式直接报错。因此"支持哪些运算符"完全由你自己决定,与类型名无关。

二、最小可用示例:三步让 Rust 结构体进入 CEL

下面以一个 IP 地址类型为例(完整可运行版本见 value.rs 的文档示例):

use cel::common::types::Type; use cel::common::value::{StaticVal, Val}; use std::any::Any; static IP: Type = Type::new_unspecified_type("ip"); #[derive(Debug)] struct Ip(u32); impl Val for Ip { fn get_type(&self) -> &Type { Self::cel_type() } fn cel_type() -> &'static Type { &IP } fn equals(&self, other: &dyn Val) -> bool { other.downcast_ref::<Ip>().is_some_and(|o| o.0 == self.0) } fn clone_as_boxed<'v>(&self) -> Box<dyn Val + 'v> { Box::new(Ip(self.0)) } fn as_any(&self) -> Option<&dyn Any> { Some(self) } } impl StaticVal for Ip {}

三个要点:

  1. 类型名用static常量:Type::new_unspecified_type("ip")需要一个&'static str,这也是类型构造函数的约束;
  2. as_any返回Some(self):这让downcast_ref::<Ip>()能把dyn Val还原成你的具体类型;
  3. 实现StaticVal空标记:它承诺你的类型是'static且实现了as_any,是后续用add_overload!声明函数重载时的必备条件。

三、注册自定义类型:Env::add_type 的正确姿势

值本身不需要注册就能被求值,但注册类型之后,表达式才能按名字引用它,例如type(addr) == ip这类类型判断。

let mut env = Env::stdlib(); env.add_type(IP.to_owned()).unwrap(); let program = env.compile("type(addr) == ip").unwrap();

相关 API 位于 Env::add_type:

  • 名称冲突会返回DeclarationError::TypeConflict(重复注册同一个类型是允许的);
  • 若想让 CEL 里能直接写MyStruct{field: 1}这样的结构体字面量,请用StructDef并配合add_field注册字段定义;
  • 把值传给表达式用 Context::add_variable_as_val:context.add_variable_as_val("addr", Box::new(Ip(0x7f000001)))。更多变量用法可参考示例 example/src/variables.rs。

四、为自定义类型添加运算符:as_* 能力访问器一览表 🔍

想让MyPoint + MyPoint或a < b在表达式里工作?实现对应能力 trait(都在 common/traits.rs 中),然后在Val里让对应的as_*返回Some(self):

CEL 表达式Val 访问器能力 trait
a + bas_adderAdder::add
a - bas_subtractorSubtractor::sub
a * bas_multiplierMultiplier::mul
a / bas_dividerDivider::div
a % bas_modderModder::modulo
-aas_negatorNegator::negate
a < b等排序比较as_comparerComparer::compare
x in a(注意在右侧)as_containerContainer::contains
a[i]、a.f、has(a.f)as_indexerIndexer::get
a.all(...)等推演as_iterableIterable::iter
size(a)(list/map)as_sizerSizer::size

例如让两个点相加,只需实现Adder,并在Val中重写:

fn as_adder<'b, 'v>(&'b self) -> Option<&'b (dyn Adder + 'v)> { Some(self) }

⚠️ 注意两个细节:

  • 右操作数自己决定接受类型:在add里用rhs.downcast_ref::<Point>()判断,不匹配就返回错误;
  • 生命周期'v:产生值的方法签名带'b/'v两个生命周期,允许结果借用操作数的数据(如返回字段引用)而不是强制拷贝,返回CowVal<'b, 'v>即可。

五、实现字段访问与 has() 检查:Indexer trait

对point.x、has(point.z)这类字段访问,实现Indexer并在as_indexer中返回Some(self):

impl Indexer for Point { fn get<'b, 'v>(&'b self, field: &dyn Val) -> Result<CowVal<'b, 'v>, ExecutionError> { match field.downcast_ref::<CelString>().map(CelString::inner) { Some("x") => Ok(CowVal::Borrowed(&self.x)), Some("y") => Ok(CowVal::Borrowed(&self.y)), _ => Err(ExecutionError::no_such_key("field")), } } fn steal<'v>(self: Box<Self>, field: &dyn Val) -> Result<Box<dyn Val + 'v>, ExecutionError> { self.get(field).map(CowVal::into_owned) } }

这套写法有一个"免费午餐":当get对不存在的字段返回no_such_key错误时,has(p.z)会自动得到false而不是报错——这正是官方文档示例验证的行为(见 traits.rs 的 Indexer 示例)。另外steal只用于无人引用的内置容器,应用类型直接复用get即可。

六、相等性与错误处理最佳实践

  • equals必须对称:解释器只对左操作数调用它,所以若你的类型要与另一种类型相等,两边都要实现(参照内置类型1 == 1.0的双向实现);
  • 错误的标准姿势:不支持的右操作数,按语义返回ExecutionError::UnsupportedBinaryOperator、ValuesNotComparable或NoSuchOverload。对比较、in、索引和字段访问,解释器会把这些错误统一报告为带运算符名的NoSuchOverload(与 cel-go 行为一致,见 traits.rs 模块说明);
  • 不要漏掉Debug + Send + Sync:它们是Val的超 trait,自定义类型不满足就无法编译。

七、常见坑与排错清单 ✅

现象原因与对策
==永远为false没重写equals(默认返回false)
函数重载里拿不到具体类型忘记as_any返回Some(self)或漏了impl StaticVal
表达式里写类型名报未定义类型没有通过Env::add_type注册到环境
运算符报NoSuchOverload对应的as_*仍返回默认的None
type(x) == my_type不成立值上的get_type()与注册到Env的类型常量不一致

小结

掌握 cel-rust 自定义类型的完整路径其实只有四步:实现Val(身份 + 拷贝 + 相等)→ 用StaticVal开启类型还原 →Env::add_type注册类型名 → 按需实现能力 trait 解锁运算符。值系统的核心源码集中在 cel/src/common/value.rs 与 cel/src/common/traits.rs,配合 Env 文档 中的结构体定义示例,足以覆盖绝大多数业务定制场景。

【免费下载链接】cel-rust

Common Expression Language interpreter written in Rust

项目地址:https://gitcode.com/gh_mirrors/ce/cel-rust
点击查看免费下载

相关推荐

上一篇:白山云、华为云、腾讯 TaoToken:KAT 生态里的第三方玩家图鉴
下一篇:同样做 AI 内容生成:AirCard 与稿定AI、AI 聚合平台们差在哪

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/11/2 15:03:24

Dolphin-Mistral 24B威尼斯版:掌控你的无审查AI

Dolphin-Mistral 24B威尼斯版&#xff1a;掌控你的无审查AI 导语&#xff1a;Dolphin-Mistral 24B威尼斯版正式发布&#xff0c;这款由dphn.ai与Venice.ai联合打造的大语言模型以"无审查"和"用户控制权"为核心卖点&#xff0c;挑战现有AI服务模式&#xff…

作者头像 李华
网站建设 2026/11/2 14:55:48

C++仿C#实现事件处理

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/11/2 14:51:52

自研轻量级服务监控雷达:主动探活、负载感知与告警实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/11/2 14:51:48

PLFM_RADAR:可编程线性调频雷达完整信号处理链路解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/11/2 14:51:31

用Python自建平台雷达:网页变化监控与自动通知实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/11/2 14:48:06

Appium+Python+Unittest移动端自动化测试实战指南

1. 为什么选择AppiumPythonUnittest组合 移动端UI自动化测试领域存在多种技术方案&#xff0c;但AppiumPythonUnittest的组合在测试圈内一直保持着高人气。这套技术栈的核心优势在于其跨平台特性与Python生态的结合。Appium基于WebDriver协议扩展&#xff0c;支持iOS、Android和…

作者头像 李华