E0735 错误代码解析:为什么结构体、枚举和联合体的类型参数默认值不能使用 `Self`
本篇文章以 rustc 错误代码文档 E0735.md 为主体,结合编译器源码(
rustc_resolve名称解析模块)深入讲解该错误的触发场景、底层原理与修复方法。读者读完后将掌握 E0735 的完整语义、Self在泛型默认值中的合法使用边界,以及如何在实战中定位并修复此类编译错误。
错误概览
E0735 是 rustc 在名称解析(name resolution)阶段报告的一类编译错误,其官方描述为:
Type parameter defaults cannot use
Selfon structs, enums, or unions. (在结构体、枚举或联合体上,类型参数的默认值不能使用Self。)
当你在 struct、enum、union 的泛型参数默认值中引用 Self 时,编译器会拒绝这段代码。这是 Rust 对 ADT(Algebraic Data Type,代数数据类型)泛型默认值施加的一项明确限制,与 trait 中允许 Self 作为默认值(如 trait Add<Rhs = Self>)的规则形成鲜明对比。
触发场景:一个典型的错误示例
错误代码文档给出了最直接的复现代码:
struct Foo<X = Box<Self>> {
field1: Option<X>,
field2: Option<X>,
}
// error: type parameters cannot use `Self` in their defaults.
这里 Foo<X = Box<Self>> 试图把类型参数 X 的默认值设为 Box<Self>——即"一个装着 Foo 自身的盒子"。从直觉上看,这似乎是想表达某种递归或自引用的类型结构,但 rustc 会直接拒绝:type parameters cannot use Self in their defaults(类型参数不能在默认值中使用 Self)。
编译时你看到的完整报错如下(由 diagnostics/mod.rs 中的诊断定义生成):
error[E0735]: type parameters cannot use `Self` in their defaults
--> src/main.rs:1:20
|
1 | struct Foo<X = Box<Self>> {
| ^^^^
为什么禁止:从源码看 Self 的本质
要理解这条限制,需要先明白一个关键事实:Self 本质上就是另一个类型参数。在 ADT 的泛型列表处理过程中,Self 只有在所有类型参数都被提供之后才是"良定义"的。这一点在 late.rs 的源码注释中说得非常直白:
// rust-lang/rust#61631: The type `Self` is essentially
// another type parameter. For ADTs, we consider it
// well-defined only after all of the ADT type parameters have
// been provided. Therefore, we do not allow use of `Self`
// anywhere in ADT type parameter defaults.
对应 Rust 社区 issue rust-lang/rust#61631,其逻辑是:
- 在解析 ADT 的类型参数默认值时,
GenericArgs(泛型实参列表)只能按顺序提供已经声明过的前面的类型参数; - 而
Self相当于排在所有参数之后的隐式参数,只有当全部显式类型参数都确定后它才有确定含义; - 因此在默认值中引用
Self会产生"尚未定义就使用"的前向引用(forward reference)问题。
同样的源码注释还强调了这条禁令的适用范围:
// (We however cannot ban `Self` for defaults on *all* generic
// lists; e.g. trait generics can usefully refer to `Self`,
// such as in the case of `trait Add<Rhs = Self>`.)
也就是说,trait 的泛型默认值可以合法使用 Self,例如标准库中 trait Add<Rhs = Self> 就是常见范式;禁令只针对 ADT。
底层实现:ForwardGenericParamBan 机制
E0735 的检查发生在 rustc 名称解析阶段(rustc_resolve crate)的 late resolution 中。核心机制是一个名为 ForwardGenericParamBan(前向泛型参数禁令)的 Rib。
1. 建立禁令列表
在 late.rs 的 visit_generic_params 中,编译器把当前泛型列表里的所有参数先放入"禁令 Rib",随后逐个移除已处理完的参数:
fn visit_generic_params(&mut self, params: &'ast [GenericParam], add_self_upper: bool) {
// For type parameter defaults, we have to ban access
// to following type parameters, as the GenericArgs can only
// provide previous type parameters as they're built. We
// put all the parameters on the ban list and then remove
// them one by one as they are processed and become available.
let mut forward_ty_ban_rib =
Rib::new(RibKind::ForwardGenericParamBan(ForwardGenericParamBanReason::Default));
let mut forward_const_ban_rib =
Rib::new(RibKind::ForwardGenericParamBan(ForwardGenericParamBanReason::Default));
for param in params.iter() {
match param.kind {
GenericParamKind::Type { .. } => {
forward_ty_ban_rib
.bindings
.insert(Ident::with_dummy_span(param.ident.name), Res::Err);
}
GenericParamKind::Const { .. } => {
forward_const_ban_rib
.bindings
.insert(Ident::with_dummy_span(param.ident.name), Res::Err);
}
GenericParamKind::Lifetime => {}
}
}
...
}
注意其中关键的一步——只有传入 add_self_upper 参数为 true 时,Self(kw::SelfUpper)才会被加入禁令列表:
if add_self_upper {
// (`Some` if + only if we are in ADT's generics.)
forward_ty_ban_rib.bindings.insert(Ident::with_dummy_span(kw::SelfUpper), Res::Err);
}
2. 何时对 Self 启用禁令
add_self_upper 的取值取决于当前是否处于 ADT 的泛型列表中,见 late.rs:
fn visit_generics(&mut self, generics: &'ast Generics) {
self.visit_generic_params(&generics.params, self.diag_metadata.current_self_item.is_some());
for p in &generics.where_clause.predicates {
self.visit_where_predicate(p);
}
}
只有当 current_self_item.is_some()(即当前正在处理结构体、枚举、联合体等拥有 Self 的项的泛型)时,Self 才进入禁令 Rib。这也解释了为什么 trait 泛型中 Self 合法——trait 场景下该路径不会把 Self 放入禁令列表。
3. 命中禁令后的诊断
当解析器在禁令 Rib 中解析到 Self 时,ident.rs 的 validate_res_from_ribs 会报告对应的解析错误:
// An invalid forward use of a generic parameter from a previous default
// or in a const param ty.
if let RibKind::ForwardGenericParamBan(reason) = all_ribs[rib_index].kind {
if let Some(span) = finalize {
let res_error = if rib_ident.name == kw::SelfUpper {
ResolutionError::ForwardDeclaredSelf(reason)
} else {
ResolutionError::ForwardDeclaredGenericParam(rib_ident.name, reason)
};
self.report_error(span, res_error);
}
assert_eq!(res, Res::Err);
return Res::Err;
}
随后在 diagnostics/impls.rs 中,ForwardDeclaredSelf 按原因分派到具体诊断。当 ForwardGenericParamBanReason 为 Default(即类型参数默认值场景)时,生成 E0735:
ResolutionError::ForwardDeclaredSelf(reason) => match reason {
ForwardGenericParamBanReason::Default => {
self.dcx().create_err(diagnostics::SelfInGenericParamDefault { span })
}
ForwardGenericParamBanReason::ConstParamTy => self
.dcx()
.create_err(diagnostics::SelfInConstGenericTy { span, enable_feature: false }),
},
而诊断本身在 diagnostics/mod.rs 中定义,最终呈现的报错文本即:
#[derive(Diagnostic)]
#[diag("generic parameters cannot use `Self` in their defaults", code = E0735)]
pub(crate) struct SelfInGenericParamDefault {
#[primary_span]
pub(crate) span: Span,
}
值得一提:同一个 ForwardGenericParamBan 机制还承担着另一类前向引用检查——当原因变为 ConstParamTy 时(const 参数类型引用 Self),会触发另一条诊断(不允许在 const 参数类型中使用 Self,在开启 min_adt_const_params 特性后有对应的放宽路径)。可见 E0735 属于"泛型默认值前向引用禁令"体系中的一个分支。
如何修复
修复 E0735 的核心思路是:不要在 ADT 类型参数默认值中引用 Self,改用具名的具体类型。
方案一:用具体类型替换 Self
// 错误:默认值引用 Self
struct Foo<X = Box<Self>> {
field1: Option<X>,
field2: Option<X>,
}
// 修复:使用明确的具体类型作为默认值
struct Foo<X = Box<Foo>> {
field1: Option<X>,
field2: Option<X>,
}
注意这里把 Self 替换成了类型名 Foo 本身,编译即可通过。之所以能这样写,是因为在 ADT 的类型参数默认值中,使用该 ADT 自身的具名类型是允许的——禁令只针对 Self 这个关键字形态。
方案二:去掉默认值,改用显式泛型参数
如果 Box<Self> 只是示例,实际并不需要默认值,直接移除默认值声明即可:
struct Foo<X> {
field1: Option<X>,
field2: Option<X>,
}
调用时显式指定类型参数,例如 Foo<Box<Foo>>。
方案三:把"自引用默认值"的需求转移到 trait 上
如果确实需要"默认情况下引用自身"的语义,可以考虑用 trait 表达,因为 trait 的类型参数默认值允许 Self:
trait Wrapper<X = Box<Self>> {
fn wrap(&self) -> X;
}
struct Foo;
impl Wrapper for Foo {
fn wrap(&self) -> Box<Foo> {
Box::new(Foo)
}
}
这与 trait Add<Rhs = Self> 的写法一脉相承,是标准库也采用的做法。
合法与非法用法对照
| 场景 | 写法 | 是否触发 E0735 |
|---|---|---|
结构体类型参数默认值引用 Self |
struct Foo<X = Box<Self>> |
✅ 触发 |
枚举类型参数默认值引用 Self |
enum E<X = Option<Self>> |
✅ 触发 |
联合体类型参数默认值引用 Self |
union U<X = ManuallyDrop<Self>> |
✅ 触发 |
trait 类型参数默认值引用 Self |
trait Add<Rhs = Self> |
❌ 合法 |
| 结构体默认值引用自身具名类型 | struct Foo<X = Box<Foo>> |
❌ 合法 |
| 默认值引用前面已声明的参数 | struct Foo<A, B = Option<A>> |
❌ 合法 |
| 默认值引用后面未声明的参数 | struct Foo<A = B, B> |
✅ 触发同类前向引用错误 |
上表最后一行对应的是同机制下的另一条错误(前向引用后续泛型参数,ForwardDeclaredGenericParam),说明这套禁令不只是针对 Self,而是面向所有"默认值中引用尚未就绪参数"的情况。
小结
E0735 是 rustc 名称解析阶段对 ADT 泛型默认值的一项防御性检查,其背后是对 Self 作为"隐式尾随类型参数"语义的严谨约束:在结构体、枚举、联合体的泛型默认值中,Self 尚未被完整定义,因此被明确禁止;而 trait 泛型默认值则可以自由使用 Self。理解这一点,不仅能快速定位修复此类编译错误,也能更深入地理解 Rust 泛型系统与名称解析(ForwardGenericParamBan Rib 机制)的设计细节。
相关仓库路径速查
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0631
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
video-shotcraftAI宣传片skill,使用 Remotion 制作电影级产品视频:提供106 张镜头配方卡和可复用的视频魔板。适用于 Claude Code 与 Codex以及所有其他智能体Markdown00
HivisionIDPhotos⚡️HivisionIDPhotos: a lightweight and efficient AI ID photos tools. 一个轻量级的AI证件照制作算法。Python09
DragonOSDragonOS is an operating system developed from scratch using Rust, with Linux compatibility. It is designed for **Serverless** scenarios. 使用Rust从0自研内核,具有Linux兼容性的操作系统,面向云计算Serverless场景而设计。Rust00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00