Rust 编译器错误码 E0087 详解:多余的泛型类型参数与 E0107 诊断机制
【免费下载链接】rustEmpowering everyone to build reliable and efficient software.项目地址: https://gitcode.com/GitHub_Trending/ru/rust
本文围绕 Rust 编译器历史错误码 E0087("函数被传入过多类型参数")展开,说明其触发条件、为何在当前编译器中不再被发出、以及取而代之的 E0107 错误的完整语义,并结合 rustc 源码中的泛型参数数量诊断实现,帮助读者在遇到wrong number of type arguments类报错时快速定位与修复问题。
一、E0087 的核心语义:类型参数数量必须精确匹配
错误码 E0087 的定义非常直接:给一个泛型项(函数、类型等)提供了过多的类型实参,数量超过了其声明的类型参数个数。
官方错误码文档 E0087.md 给出的经典错误示例是:
fn foo<T>() {} fn main() { foo::<f64, bool>(); // error: wrong number of type arguments: // expected 1, found 2 }fn foo<T>()只声明了 1 个类型参数T,但调用点通过 Turbofish 语法::<f64, bool>提供了 2 个类型实参。文档中的修复原则只有一句话,却抓住了本质:
The number of supplied arguments must exactly match the number of defined type parameters. (提供的实参数量必须与定义的类型参数数量完全一致。)
即调用foo的正确写法是foo::<f64>()或省略实参foo()(当类型可从上下文推断时)。
二、关键事实:E0087 已不再由编译器发出
E0087.md 的第一行就明确标注:
Note: this error code is no longer emitted by the compiler. (注意:该错误码不再由编译器发出。)
这意味着:在现代 Rust 版本中,你永远不可能再直接看到error[E0087]。编译器把"泛型实参数量不对"这一整类错误统一归并到了 E0107 之下。这一归并关系在错误码注册表中也有旁证——rustc_error_codes 的 lib.rs 中,早期错误码 E0035/E0036 被注释为merged into E0087/E0089,而 E0087 所在的"泛型参数数量"这一片区最终又进一步收敛到 E0107,形成了一条清晰的错误码演化链:
E0035 / E0036 ──(合并)──> E0087 / E0089 ──(统一归并)──> E0107 数量不足 数量过多 数量不符(统一诊断)因此,对于维护旧代码库或阅读旧 issue 时遇到的 E0087,正确理解方式是把它当作E0107 的"历史别名":凡是历史上报 E0087 的场景,今天都会以 E0107 的形式呈现。
三、现代编译器中的实际表现:E0107
当前编译器发出的是error[E0107],消息措辞也比历史版本更精确——它不仅区分"数量过多/过少",还区分生命周期参数、类型参数、常量参数三类实参。E0107.md 汇总了完整的行为:
struct Foo<T> { x: T } struct Bar { x: Foo } // error: wrong number of type arguments: // expected 1, found 0 struct Baz<S, T> { x: Foo<S, T> } // error: wrong number of type arguments: // expected 1, found 2 fn foo<T, U>(x: T, y: U) {} fn f() {} fn main() { let x: bool = true; foo::<bool>(x); // error: expected 2, found 1 foo::<bool, i32, i32>(x, 2, 4); // error: expected 2, found 3 f::<'static>(); // error: wrong number of lifetime arguments // expected 0, found 1 }注意第三行f::<'static>():对没有任何生命周期参数的函数提供生命周期实参,会报wrong number of lifetime arguments。这与 E0087 时代只谈"type arguments"的粗糙措辞形成了对比。
仓库中的 UI 测试可以佐证真实诊断消息的格式。tests/ui/type-alias-enum-variants/enum-variant-generic-args.stderr 中记录了编译器对类型别名变体上多余 Turbofish 实参的输出:
error[E0107]: type alias takes 0 generic arguments but 1 generic argument was supplied --> $DIR/enum-variant-generic-args.rs:64:5 | LL | AliasFixed::<()>::TSVariant(());可以看到消息模板是<def_kind> takes <N> generic argument(s) but <M> generic argument(s) was/were supplied,并且精确标注了出错标识符的 span。
对应的正确写法(参数数量与声明一致):
struct Foo<T> { x: T } struct Bar<T> { x: Foo<T> } // ok! struct Baz<S, T> { x: Foo<S>, y: Foo<T> } // ok! fn foo<T, U>(x: T, y: U) {} fn f() {} fn main() { let x: bool = true; foo::<bool, u32>(x, 12); // ok! f(); // ok! }四、源码级剖析:WrongNumberOfGenericArgs诊断实现
从源码结构看,E0107(即历史 E0087 场景)的完整诊断逻辑集中在 rustc_hir_analysis 的 wrong_number_of_generic_args.rs 中。理解该文件即可理解"数量不匹配"报错的每一处细节。
4.1 状态结构:一次诊断需要哪些输入
核心是WrongNumberOfGenericArgs结构体(源码 L10-L33),它把判定所需的全部上下文一次性收集齐:
| 字段 | 含义 |
|---|---|
gen_params | 目标类型/特征期望的泛型参数(ty::Generics) |
gen_args | 用户在调用点实际提供的hir::GenericArgs |
gen_args_info | 描述缺失/冗余实参的种类与数量(见下) |
angle_brackets | 角括号<...>的三种形态(见下) |
path_segment | 出错的 HIR 路径段,用于定位 span |
params_offset | 处理缺失/冗余实参时的参数索引偏移(是否含Self、生命周期参数数量等) |
4.2 两类状态枚举:精确定位"缺了什么"与"多给了什么"
GenericArgsInfo(源码 L51-L86)区分四种情形:
MissingLifetimes { num_missing_args }:缺生命周期实参ExcessLifetimes { num_redundant_args }:多余生命周期实参MissingTypesOrConsts { num_missing_args, num_default_params, args_offset }:缺类型/常量实参ExcessTypesOrConsts { num_redundant_args, num_default_params, args_offset, synth_provided }:多余类型/常量实参——E0087 的场景就落在ExcessTypesOrConsts这一分支上
其中num_default_params字段值得注意:由于 Rust 支持类型参数默认值(如struct Foo<T = u8>),"期望数量"存在上下界,诊断消息会相应使用 "at least N" / "at most N" 的措辞(由get_quantifier_and_bound方法生成,见 源码 L305-L325)。而synth_provided则标记用户是否显式写入了impl Trait这类编译器不允许的合成实参,诊断会追加 note:impl Trait cannot be explicitly specified as a generic argument(源码 L1141-L1147)。
AngleBrackets枚举(源码 L38-L47)则刻画调用点写法的三种形态:
Missing:完全没写角括号(如struct Bar { x: Foo })Implied:没写角括号但存在被省略的实参(如&str中的省略生命周期)Available:写了角括号但实参数量不对(如foo::<f64, bool>)——E0087 示例正是这一形态
new构造函数(源码 L89-L114)通过gen_args.span_ext().is_none()来区分这三种形态:没有 span 且实参列表为空是Missing,没有 span 但存在省略实参是Implied,否则是Available。
4.3 消息与高亮:expected N与supplied M
历史文档中的注释expected 1, found 2对应的现代实现是notify方法(源码 L546-L596):它在标识符 span 上标注expected <量化词><N> <lifetime/generic> argument(s),再对已提供的实参逐个标注,最后一个标注supplied <M> ... argument(s)。
一个体现诊断细致之处的细节:当实参过多时(too_many_args_provided()),notify会提前返回、不逐个高亮实参——因为此时另有"删除冗余实参"的建议(suggest_removing_args_or_generics),二者叠加会造成标注重叠(源码 L561-L571 的注释给出了示意)。
4.4 自动修复建议:删掉多余参数
对 E0087 场景(实参过多),编译器不只报错,还会给出可应用的修复建议。suggest_removing_args_or_generics(源码 L902 起)的策略包括:
- 删除整个角括号:当冗余实参数 ≥ 全部实参数时,建议
remove the unnecessary generics,即把foo::<f64, bool>()修复为foo()。注意 源码 L1070-L1078 中有一段专门避免 ICE 的 HACK:若同一作用域内存在同名但非泛型的类型(例如derive展开产生的重复定义),空 span 建议会触发断言,故此处显式跳过; - 删除部分冗余实参:仅删除超过期望数量的那一段 span,建议文案为
remove the unnecessary generic argument(s); - 改写为关联项绑定:当目标是一个 trait,且未绑定的关联项数量恰好等于冗余实参数量时,编译器会建议"把多余实参改写为关联项绑定"(
turn the generic argument(s) into an associated item binding(s),源码 L1010-L1051)。例如对Iterator<u32>多给一个类型参数时,可提示改为<u32 as Iterator>::Item = ...这类绑定写法; - 把多余实参移到 trait 上:对关联函数调用(如
Into::into::<T>(x))给出consider moving this generic argument to the Into trait的多部分建议(suggest_moving_args_from_assoc_fn_to_trait,源码 L759-L895)。
对于实参过少的场景,对应的suggest_adding_args系列方法会基于参数名甚至上下文(函数参数位推断'_、静态/常量上下文推断'static、方法调用 Turbofish 中推断_)生成add missing generic argument(s)建议(源码 L626-L751)。
4.5 最终装配:错误码 E0107 在此被写死
诊断的最终装配发生在Diagnostictrait 实现中(源码 L1150-L1168):
let msg = self.create_error_message(); let mut err = Diag::new(dcx, level, msg); err.code(E0107); err.span(self.path_segment.ident.span); self.notify(&mut err); self.suggest(&mut err); self.show_definition(&mut err); self.note_synth_provided(&mut err);这一行err.code(E0107)从实现层面证实了第二节的结论:所有"泛型实参数量不匹配"(含历史 E0087 的"过多"场景)如今统一以 E0107 发出。此外show_definition(源码 L1094-L1138)还会在定义处追加 note,形如type defined here, with 1 generic parameter: T,把调用点与定义点关联起来。
五、遇到此类报错的排查清单
结合上述文档与源码,遇到wrong number of type/lifetime arguments(E0107)时可按以下步骤处理:
- 读 span 标注:
expected N generic argument(s)标在标识符上,supplied M标在实参上,N 与 M 的差值就是需要增删的实参数量; - 对照定义:根据 note 中
defined here, with ... parameter(s)定位定义,确认参数列表(注意区分生命周期参数与类型/常量参数,二者按顺序排列且各自计数); - 区分过少与过多:
- 过少:补齐实参,或采用编译器建议的占位符
_/ 推断(尤其是 Turbofish 中的_); - 过多(即历史 E0087):删除冗余实参;若目标是 trait,检查是否该写成关联项绑定或把实参挪到 trait 路径段上;
- 过少:补齐实参,或采用编译器建议的占位符
- 注意默认参数:带默认值的类型参数允许实参数量少于参数总数("at least N"),此时报错信息中的下界措辞来自
get_quantifier_and_bound的逻辑; - 旧文档迁移:若在旧博客/旧 issue 中看到 E0087,直接按 E0107 语义理解即可,二者修复方法完全一致。
六、参考资料索引
| 内容 | 仓库路径 |
|---|---|
| E0087 错误码文档(历史,含本文起始示例) | E0087.md |
| E0107 错误码文档(现行,含完整正/误示例) | E0107.md |
| 错误码归并注释(E0035/E0036 → E0087/E0089) | lib.rs |
| 数量不匹配诊断的完整实现 | wrong_number_of_generic_args.rs |
| 真实诊断消息样例(含 span 与消息格式) | enum-variant-generic-args.stderr |
【免费下载链接】rustEmpowering everyone to build reliable and efficient software.项目地址: https://gitcode.com/GitHub_Trending/ru/rust
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考