Serde 递归枚举加内部标签后报 E0275
递归枚举使用内部标签,并把 Self 放进 Box 或 Vec 新类型变体时,Serialize 可能无法编译。说明 TaggedSerializer 泛型递归与结构体变体的协议改动。
下面的枚举只是在表达式里增加了递归,派生 Serialize 时却可能报 E0275,错误还指向 std::io::Write:
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
#[serde(tag = "type")]
enum Expr {
Eq { field: String },
And(Vec<Expr>),
Not(Box<Expr>),
}
典型提示是 overflow evaluating the requirement。即使这次只序列化 Expr::Eq,编译器仍需要检查整套泛型实现,不能据此排除另外两个递归变体。
该组合见于 Serde 1.0.228、1.0.229。不要直接按提示不断增加 recursion_limit:这里存在会不断生成新类型的泛型递归,提高限制可能只让编译更久。
Box 解决了枚举大小,没解决 serializer 类型
Box<Expr> 使递归类型具有有限大小;它不保证派生实现的类型推导也能终止。内部标签的新类型变体需要把传入的 serializer 包装成 TaggedSerializer<S>,再让内部值序列化。递归回到 Expr 时,又会出现 TaggedSerializer<TaggedSerializer<S>>,如此继续。
因此,报错里出现 Write 或 serde_json 的 Serializer,并不等于输出缓冲区或磁盘写入出了问题。正式 Serde 1.0.229 的派生代码和标签包装器仍保留这条路径,不能把改用另一种输出缓冲区当成修复。
And(Vec<Expr>) 还涉及另一项限制:内部标签不能直接放进 JSON 数组。即使避开编译期问题,也不能指望在数组内部直接插入一个对象标签。单独派生 Deserialize 能通过,同样不代表 Serialize 支持相同形状。
用有名字的字段承接递归值
能调整消息格式时,可以改成结构体变体:
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
#[serde(tag = "type")]
enum Expr {
Eq { field: String },
And { filters: Vec<Expr> },
Not { expr: Box<Expr> },
}
此时递归子节点作为字段值交给正常字段序列化路径,外层对象负责 type 标签。例如一个取反节点的协议形状是:
{"type":"Not","expr":{"type":"Eq","field":"a"}}
这里保留了递归能力,但增加了 expr 和 filters 字段。这是协议变化,应同时修改构造代码、消费者、已有消息的读取策略和接口样例。不要只让服务端编译通过,就直接覆盖仍有旧消费者使用的格式。
若已有对外协议不能改,先定义独立的传输模型或手写转换层,明确每种变体在协议中的表示。不要通过修改依赖缓存中的 TaggedSerializer 或提高全项目递归上限来维持一个没有核对过的表示。
回查叶子节点、单层 Not、多层 Not 和包含多个子条件的 And,分别确认序列化输出与反序列化结果。编译恢复后还要检查协议兼容性;原来没有被编译出来的递归分支,往往也没有真正经过消息格式验证。


