登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  Golang >  Go问答

Go CEL unknown 与运行错误在结果处理上有什么区别

来源:17golang原创

时间:2026-10-25 06:40:39 332浏览 收藏

Go CEL unknown 与运行错误在结果处理上有什么区别

在 Go 中调用 CEL 表达式时,unknown 和运行错误都可能让结果不是普通的布尔值,但它们代表的状态完全不同:unknown 是输入或对象路径暂时没有确定,运行错误则是当前求值已经得到一个错误结果。前者通常等待补充数据或生成残留表达式,后者通常要返回错误并记录原因。

官方地址:https://pkg.go.dev/github.com/google/cel-go/cel

判断口诀:缺数据看 unknown,表达式算坏看 error;不要只看“结果不是 true”就把两者当成同一种失败。

unknown 和运行错误不是一回事

CEL 面向的是动态输入和分布式场景。某个变量在当前节点还没有到达,并不等于表达式非法,因此 CEL 用 unknown 表示“现在无法确定,但以后可能确定”的值。Go cel-go 可以通过部分激活把缺失变量标记为 unknown。

运行错误的语义不同,例如除数为零、宿主函数返回错误,或者某个操作对当前值不适用。它说明本次求值已经遇到明确问题,应用层应该按错误处理,而不是把它当成稍后补数的信号。

部分输入、PartialVars、CEL 表达式、unknown、运行错误与 Go Eval 返回值的关系说明图
图1:语义边界说明图,区分尚未确定的 unknown 与当前求值产生的运行错误。

缺失输入要用 PartialVars 表达

Env.PartialVars 会把未提供但已在环境中声明的变量标为 unknown;使用部分求值时,还要在创建 Program 时启用 cel.OptPartialEval。这套写法表达的是“输入不完整”,不是让 Go 忽略错误。

package main

import (
    "fmt"
    "log"

    "github.com/google/cel-go/cel"
)

func main() {
    // 声明两个输入;本例故意只提供 score,让 quota 保持 unknown。
    env, err := cel.NewEnv(
        cel.Variable("score", cel.IntType),
        cel.Variable("quota", cel.IntType),
    )
    if err != nil {
        log.Fatal(err)
    }

    // 编译阶段只负责得到可执行的 CEL 程序。
    ast, issues := env.Compile("score > quota")
    if issues.Err() != nil {
        log.Fatal(issues.Err())
    }
    prg, err := env.Program(ast, cel.EvalOptions(cel.OptPartialEval))
    if err != nil {
        log.Fatal(err)
    }

    // 缺少 quota 被建模为 unknown,而不是 Go error。
    partial, err := env.PartialVars(map[string]any{"score": int64(80)})
    if err != nil {
        log.Fatal(err)
    }
    out, details, err := prg.Eval(partial)
    fmt.Printf("out=%v details=%t err=%v\n", out, details != nil, err)
}

这里的重点是输入建模:如果业务真的允许字段稍后从中心服务补齐,就使用部分激活;如果字段按协议必须存在,则应在进入 CEL 前做请求校验,避免把客户端漏传误包装成 unknown。

Eval 返回值怎么分类

Program.Eval 返回 out、details 和 err。官方接口约定中,普通非错误结果是 val, details, nil;表达式求值到错误结果时是 val, details, err;程序本身无法正常执行时则可能是 nil, details, err。所以不要只写“err 非空就代表编译失败”,也不要只判断 out 是否为空。

// 统一把 CEL 结果转换成应用层状态。
out, details, err := prg.Eval(vars)
switch {
case err != nil:
    // 当前求值出现明确错误,记录 err 并结束本次决策。
    return fmt.Errorf("cel evaluation failed: %w", err)
case out == nil:
    // 没有结果通常意味着程序或配置层没有完成正确执行。
    return errors.New("cel returned no value")
default:
    // unknown 仍然是 CEL 值;业务层应按 out.Type() 或值语义继续分类。
    _ = details
    return handleCELValue(out)
}

如果需要知道 unknown 来自表达式的哪个位置,可以创建 Program 时启用 cel.OptTrackState,再从 details 读取状态。对部分输入场景,环境还提供 ResidualAst,可把已经确定的部分裁掉,只留下仍依赖未知属性的表达式。

prg.Eval 返回 out、details、err 后连接 ResidualAst 与业务结果分类的关系说明图
图2:返回值关系说明图,展示 Eval 三元组与 unknown 后续处理的静态关系。

逻辑运算会怎样传播两种状态

CEL 的逻辑运算不是简单的 Go 布尔短路复制。官方语义允许在左侧出现 unknown 或 error 时继续查看右侧,以便得到更确定的结果。例如 unknown && false 可以得到 false,unknown || true 可以得到 true。这也是 unknown 能在部分数据到达前参与决策的原因。

但算术和函数调用通常不能凭空消除未知或错误:unknown + error 会落到错误,带 unknown 的函数参数也可能让结果继续保持 unknown。因此服务层应把“等待更多数据”和“本次输入不可计算”分成不同的状态码。

状态典型来源应用层动作
unknown部分变量、对象路径尚未提供补数、生成残留表达式或返回待处理
运行错误除零、类型不适用、宿主函数报错记录原因,终止本次求值
Go 层 Eval 失败环境或 Program 配置不兼容修复程序配置并报警

常见问题

unknown 能直接当成 false 吗?不能。只有业务规则明确规定“缺失即不满足”时,才在边界层显式转换;否则会丢失后续补数机会。

details 只在 unknown 时有吗?不一定。启用 OptTrackState、成本跟踪或穷举求值等选项后,Eval 才会返回额外 details;它不是所有调用默认都有的诊断对象。

最实用的判断顺序是什么?先看 Go 层 err,再看 out 的 CEL 值语义;对可能部分求值的程序,同时保留 details,不要用一个布尔值覆盖三种状态。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>