Rust 编译器错误 E0063 详解:结构体初始化时缺字段的诊断原理与修复方式
【免费下载链接】rustEmpowering everyone to build reliable and efficient software.项目地址: https://gitcode.com/GitHub_Trending/ru/rust
本篇围绕 rustc 错误码文档 E0063.md 展开,讲清楚「结构体或类结构体枚举变体有字段未提供」这一错误的触发条件、诊断信息的完整格式(单数/复数/截断三种形态),并结合编译器类型检查源码 expr.rs 与实际回归测试 tests/ui/error-codes/E0063.rs 说明 rustc 是如何计算缺失字段并生成错误文本的。读完本文,你不仅能正确修复 E0063,还能理解诊断消息的生成逻辑与相关边界情况(..更新语法、默认字段值、私有字段、non_exhaustive等)。
E0063 的语义:每个字段必须恰好提供一次
官方错误码文档的定义非常明确:A struct's or struct-like enum variant's field was not provided.(结构体或类结构体枚举变体的某个字段未被提供)。原文档给出的错误示例:
struct Foo { x: i32, y: i32, } fn main() { let x = Foo { x: 0 }; // error: missing field: `y` }文档同时给出了正确写法——每个字段应当被恰好指定一次(Each field should be specified exactly once):
struct Foo { x: i32, y: i32, } fn main() { let x = Foo { x: 0, y: 0 }; // ok! }这里有两个隐含规则值得强调:
- 不提供会报错(E0063):显式结构体初始化表达式必须覆盖全部字段;
- 重复提供也会报错:字段被多次指定是另一条诊断(
FieldMultiplySpecifiedInInitializer),不是 E0063,但两者共同构成「恰好一次」的约束。
真实诊断输出:单数、复数与截断三种形态
仓库中的回归测试 E0063.rs 特意构造了四种结构体,分别覆盖单字段缺失、双字段缺失、以及缺失数量超过 3 个时的截断展示。其期望输出 E0063.stderr 展示了 E0063 消息的完整格式:
error[E0063]: missing field `x` in initializer of `SingleFoo` --> $DIR/E0063.rs:30:13 | LL | let w = SingleFoo { }; | ^^^^^^^^^ missing `x` error[E0063]: missing fields `y` and `z` in initializer of `PluralFoo` --> $DIR/E0063.rs:32:13 | LL | let x = PluralFoo {x: 1}; | ^^^^^^^^^ missing `y` and `z` error[E0063]: missing fields `a`, `b`, `y` and 1 other field in initializer of `TruncatedFoo` --> $DIR/E0063.rs:34:13 | LL | let y = TruncatedFoo{x:1}; | ^^^^^^^^^^^^ missing `a`, `b`, `y` and 1 other field error[E0063]: missing fields `a`, `b`, `c` and 2 other fields in initializer of `TruncatedPluralFoo` --> $DIR/E0063.rs:36:13 | LL | let z = TruncatedPluralFoo{x:1}; | ^^^^^^^^^^^^^^^^^^ missing `a`, `b`, `c` and 2 other fields error: aborting due to 4 previous errors For more information about this error, try `rustc --explain E0063`.可以归纳出三条格式规则:
| 缺失数量 | 主消息 | span 标签 |
|---|---|---|
| 1 个 | missing fieldxin initializer ofT| `missing `x | |
| 2 个 | missing fieldsyandzin initializer ofT| `missing `y` and `z | |
| 3 个 | 全部列出 | 全部列出 |
| 超过 3 个 | 只列前 3 个,追加and N other field(s) | 同样截断 |
注意测试中TruncatedFoo(缺失 a、b、y、z 四个字段)的消息是missing fieldsa,b,yand 1 other field——缺失字段名按排序后的顺序取前 3 个,且field/fields、other field/other fields都会随数量做单复数变化。诊断末尾的固定提示For more information about this error, tryrustc --explain E0063.则指向 rustc 错误码文档体系,本文所依据的 E0063.md 正是该体系的一个成员(位于compiler/rustc_error_codes/src/error_codes/目录下,每个错误码一个 Markdown 文件)。
源码剖析:rustc 如何发现并报告缺失字段
E0063 的完整生成链路位于 HIR 类型检查阶段,核心函数是 check_expr_struct_fields(compiler/rustc_hir_typeck/src/expr.rs)。
第一步:建立「剩余字段」集合,边检查边消耗
类型检查开始时,编译器先把变体(variant)的所有字段收集进一个 map,作为「尚未提供」的集合:
let mut remaining_fields = variant .fields .iter_enumerated() .map(|(i, field)| (field.ident(tcx).normalize_to_macros_2_0(), (i, field))) .collect::<UnordMap<_, _>>();见 expr.rs 第 1900-1904 行。随后遍历初始化表达式中写出的每个字段:若字段名能在remaining_fields中找到,就remove掉并继续对该字段表达式做类型检查;找不到则走另一条错误分支——字段名重复时报告FieldMultiplySpecifiedInInitializer,否则报告未知字段(report_unknown_field),见 expr.rs 第 1934-1951 行。
也就是说:E0063 本质上是循环结束后remaining_fields非空的检测结果——凡是「定义里有、表达式里没写」的字段都留在这个集合中。
第二步:按表达式尾部形态分流
遍历完成后,编译器根据结构体表达式尾部(StructTailExpr)分四种情况处理(见 expr.rs 第 2004-2263 行):
Base(expr)(即..base函数式更新语法):剩余字段由base的值补齐,E0063 不会触发,转而检查base表达式的类型;DefaultFields(bare..,默认字段值):这是 nightly 上的#![feature(default_field_values)]能力。若没有启用该特性,报告的是另一条诊断(BaseExpressionDoubleDot);启用后,只有「没有默认值且又没提供」的字段才会被单独报告为 mandatory 缺失;NoneWithError:表达式存在语法错误导致部分字段没解析出来时,编译器只标记 tainted 而不报 E0063,避免叠加虚假错误(源码注释给出了StructName { foo(), bar: 2 }这类例子,见 expr.rs 第 2211-2223 行);None(没有尾部):这才是标准 E0063 路径。
标准路径上的判断条件很典型:
rustc_hir::StructTailExpr::None => { if adt_kind != AdtKind::Union && !remaining_fields.is_empty() && !variant.field_list_has_applicable_non_exhaustive() { /* 报告缺失字段 */ } }见 expr.rs 第 2224-2262 行。两个守卫条件对应两个重要边界情况:
- union 豁免:union 初始化要求恰好一个字段,字段数量不对时报告的是 E0784("union expressions should have exactly one field",见 expr.rs 第 1984-1992 行),不落入 E0063;
non_exhaustive豁免:带non_exhaustive标记的外部 crate 类型,其字段集对外不完整,源码中已注明「non_exhaustive 的缺失已由其他机制报告,仅针对外部模块」(注释//~ non_exhaustive already reported)。
第三步:私有字段优先提示
进入报告逻辑前还有一个可见性检查:若缺失字段中存在对当前模块不可见的私有字段,编译器改走report_private_fields,而不是直接报 E0063:
let private_fields: Vec<&ty::FieldDef> = variant .fields .iter() .filter(|field| { !field.vis.is_accessible_from(tcx.parent_module(expr.hir_id), tcx) }) .collect();见 expr.rs 第 2234-2249 行。这解释了实际开发中的一个常见体验:跨模块漏掉私有字段时,编译器倾向于先提示字段私有,避免误导你直接去补一个根本不可见的字段。
第四步:消息文本的拼接与智能建议
最终的错误构造在 report_missing_fields(expr.rs 第 2290-2367 行):
let displayable_field_names: Vec<&str> = remaining_fields.items().map(|(ident, _)| ident.as_str()).into_sorted_stable_ord(); let remaining_fields_names = match &displayable_field_names[..] { [field1] => format!("`{field1}`"), [field1, field2] => format!("`{field1}` and `{field2}`"), [field1, field2, field3] => format!("`{field1}`, `{field2}` and `{field3}`"), _ => { truncated_fields_error = format!(" and {} other field{}", len - 3, pluralize!(len - 3)); displayable_field_names .iter() .take(3) .map(|n| format!("`{n}`")) .collect::<Vec<_>>() .join(", ") } }; let mut err = struct_span_code_err!( self.dcx(), span, E0063, "missing field{} {}{} in initializer of `{}`", pluralize!(len), remaining_fields_names, truncated_fields_error, adt_ty );这段代码与上一节归纳的三种消息格式一一对应:字段名先排序(into_sorted_stable_ord),1/2/3/更多四种模式分别拼接,超过 3 个时只取前 3 个并追加and N other field(s);pluralize!宏负责 field/fields 的单复数。struct_span_code_err!宏把错误码 E0063 绑定到诊断上,从而让rustc --explain E0063能定位到对应的错误码文档。
源码中还有两个进阶细节:
- nightly 默认字段值建议:如果所有剩余字段都带默认值(
field.value.is_some()),且在 nightly 构建下,诊断会附带一条机器可应用的建议——用..补齐,并提示可启用#。注意这是nightly 实验特性,稳定版不可用; - range 字面量误用建议:若初始化表达式的最后一个「字段」写成了 range 字面量(如
a..b),源码从结构看判断用户很可能想写..base,会通过suggest_fru_from_range_and_emit发出改为函数式更新语法的建议(见 expr.rs 第 2362-2366 行)。
与 E0063 相邻的边界情况速查
结合上述源码路径,可以把「初始化表达式字段不合法」的几个相邻错误码梳理清楚,便于排错时快速区分:
| 场景 | 错误码 / 诊断 | 说明 |
|---|---|---|
字段漏写(无..) | E0063 | 本文主题,remaining_fields非空 |
| 字段重复写 | FieldMultiplySpecifiedInInitializer | 「恰好一次」的另一半约束 |
| 写了不存在的字段 | report_unknown_field(E0027 系列) | 字段名不在remaining_fields中 |
| union 表达式字段数 ≠ 1 | E0784 | union 豁免于 E0063 |
对non_exhaustive外部类型构造 | StructExprNonExhaustive | 见 expr.rs 第 1847-1850 行 |
| 漏写的是私有字段 | report_private_fields | 可见性检查优先于 E0063 |
| 前序语法错误导致字段未解析 | 不报 E0063 | 仅标记 tainted,防叠加误报 |
修复建议
- 直接补齐缺失字段:错误消息已经把缺失字段名(至多前 3 个 + 数量)列出来,按提示逐字段赋值即可,这是原文档给出的标准解法;
- 使用
..base函数式更新:如果已有同类型(或满足字段兼容约束)的实例,let y = Foo { x: 0, ..old };可让剩余字段由old提供,从根本上绕开 E0063。注意 stable 上..的基础表达式必须与外层是同一类型(不同类型的基础表达式受type_changing_struct_update等实验特性控制,源码中对同 ADT 判断见 expr.rs 第 2180-2194 行); - 检查
..与 range 字面量混淆:如果消息附近附带 FRU 建议,多半是把..base误写成了a..b; - nightly 用户可考虑默认字段值:若字段都定义了默认值,nightly 下可启用
#![feature(default_field_values)]后使用 bare..,编译器会自动给出该建议。
小结
E0063 是 rustc 对「结构体/类结构体枚举变体初始化必须覆盖全部字段」这一规则的强制检查,其完整链路为:check_expr_struct_fields 用remaining_fields集合差分出未提供字段 → 依据表达式尾部(None/Base/DefaultFields/NoneWithError)与 ADT 种类(union、non_exhaustive、字段可见性)分流 → 由 report_missing_fields 拼出带单复数与截断规则的 E0063 消息。行为与期望可对照仓库测试 tests/ui/error-codes/E0063.rs 与 tests/ui/error-codes/E0063.stderr 验证;语义说明则出自错误码文档 compiler/rustc_error_codes/src/error_codes/E0063.md。理解了这条链路后,遇到 E0063 既能快速修复,也能准确判断编译器「该报没报」背后的守卫条件。
【免费下载链接】rustEmpowering everyone to build reliable and efficient software.项目地址: https://gitcode.com/GitHub_Trending/ru/rust
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考