Rust 编译器错误 E0806 详解:Externally Implementable Item 声明与实现签名不兼容
【免费下载链接】rustEmpowering everyone to build reliable and efficient software.项目地址: https://gitcode.com/GitHub_Trending/ru/rust
导读:E0806 是 rustc 针对「外部可实现项(Externally Implementable Item, EII)」引入的诊断——当你在某项上标注了 EII 属性(例如#[eii(foo)],或稳定代码中常见的#[panic_handler])但其实现签名与声明不一致时,编译器会报出该错误。本文从官方错误文档出发,结合当前仓库中compare_eii.rs的源码实现,完整讲解 EII 机制的背景、E0806 的触发条件、典型场景(no_std下的 panic handler)以及编译器的具体检查流程,帮助你快速定位并修复此类签名不匹配问题。
1. 错误定义:什么情况下触发 E0806
E0806 官方错误文档 给出的定义是:
An externally implementable item is not compatible with its declaration. (一个外部可实现项与其声明不兼容。)
其核心机制是:声明方用#[eii(name)]属性声明"这个项允许被外部实现",实现方则用#[name]属性提供具体实现。实现函数的签名必须与声明项的签名完全兼容(参数个数、参数类型、返回类型、生命周期数量等),否则编译器报 E0806。
2. 背景:什么是 Externally Implementable Item(EII)
EII 是 Rust 中一种允许标准库/基础库把"某个项的签名固定下来、实现留给下游 crate"的机制。它取代了旧式#[lang]标记方案,典型应用就是#[panic_handler]:core固定了panic_impl的签名,用户在自己的no_stdcrate 里用#[panic_handler]提供实现。
在当前仓库中,EII 处于不稳定特性阶段:
- 特性开关为
extern_item_impls,在 unstable.rs 中登记为incomplete状态,追踪 issue 为 125418; - 对应的两个内建宏
#[eii]与#[unsafe_eii]定义在 core 的宏模块,均标注#[unstable(feature = "extern_item_impls", issue = "125418")],并标记为#[rustc_builtin_macro](编译器内建宏,无 Rust 侧展开体); - 另有
eii_internals这一 internal 特性支撑#[eii_declaration]等内部细节。
适用前提:直接写
#![feature(extern_item_impls)]+#[eii(foo)]需要 nightly 工具链;而#[panic_handler]在no_std场景下是稳定路径,但同样受 EII 兼容规则约束,是日常开发中最常触发 E0806 的地方。
3. 官方文档的最小复现与修复
错误文档给出的最简触发示例(完整代码见 E0806.md):
#![feature(extern_item_impls)] #[eii(foo)] fn x(); #[foo] fn y(a: u64) -> u64 { //~^ ERROR E0806 a } fn main() {}这里y声称实现 EIIfoo,但y的签名(u64) -> u64与声明x(无参、无显式返回类型)不符,因此在y上报 E0806。修复方式是让实现签名匹配声明:
#![feature(extern_item_impls)] #[eii(foo)] fn x(); #[foo] fn y() {} fn main() {}经验法则:看到 E0806 时,先找到声明项(通常由#[eii]标记,或标准库中的固定声明),逐一对比实现项的参数个数、参数类型、返回类型与生命周期。
4. 典型实战场景:#[panic_handler]写错签名
官方文档特别指出,#[panic_handler]签名写错是触发 E0806 的常见方式。在no_std项目中,panic handler 的签名由core决定:
#![no_std] #[panic_handler] fn on_panic() -> ! { //~^ ERROR E0806 loop {} } fn main() {}无参版本会报 E0806,正确写法必须携带&PanicInfo参数:
#![no_std] #[panic_handler] fn on_panic(info: &core::panic::PanicInfo<'_>) -> ! { loop {} } fn main() {}这个要求在源码中可以直接印证:core/src/panicking.rs 中,panic_fmt通过一个unsafe extern "Rust"块声明了#[lang = "panic_impl"]的固定签名:
unsafe extern "Rust" { #[lang = "panic_impl"] fn panic_impl(pi: &PanicInfo<'_>) -> !; }从源码结构看,panic_impl这个"声明"就是你写#[panic_handler]时必须兼容的那个 EII 声明——参数必须是&PanicInfo<'_>,返回!。少写参数(如上文fn on_panic())即被判定为签名不兼容。
5. 源码剖析:rustc 如何检查 EII 兼容性
E0806 的检查逻辑集中在 rustc_hir_analysis/src/check/compare_eii.rs。文件开头注释说明它"与compare_impl_item非常相似——同样是拿一份签名声明去比对实现,区别在于 EII 比对的是自由项(freestanding item),不涉及 self 类型"。
对函数型 EII,核心入口是compare_eii_function_types(L37-L156),检查按顺序分四步:
5.1 目标种类检查(check_eii_target)
确认实现项与声明项的"种类"一致(L227-L268):函数对函数、静态项对静态项。特别地,两个Static之间还比较可变性(mutability)与安全性(safety),不一致时分别报EiiDefkindMismatchStaticMutability/EiiDefkindMismatchStaticSafety。
5.2 结构兼容性检查(check_is_structurally_compatible)
在走类型推断之前,先做三项"廉价"的结构性校验(L275-L286):
- 禁止泛型参数(
check_no_generics,L289-L315):EII 实现不能带泛型参数。源码注释解释了细节——由#[eii]宏自动生成的实现直接引用外部项,这类"内部生成"的泛型实现会被跳过以免重复报错; - 参数个数检查(
check_number_of_arguments,L355-L451):直接比较tcx.fn_sig的inputs()长度。不一致时发出的正是带 E0806 代码的struct_span_code_err!,消息形如`{name}` has 1 parameter(s) but #[eii_name] requires it to have 0,并在声明处标注 "requires N parameter(s)"、在实现处标注 "expected N, found M"、在属性处标注 "required because of this attribute"; - 早期绑定生命周期数量检查(
check_early_region_bounds,L317-L353):实现与声明的 lifetime 参数个数必须一致,不一致报LifetimesOrBoundsMismatchOnEii。
5.3 完整签名的子类型推导
结构性检查通过后,编译器构造一个推断上下文InferCtxt与ObligationCtxt,取出声明的函数签名declaration_sig与实现的签名external_impl_sig,执行:
let result = ocx.sup(&cause, param_env, declaration_sig, external_impl_sig);即验证declaration_sig <: external_impl_sig(声明签名是实现签名的子类型)。推导失败时,report_eii_mismatch(L453-L525)发出 E0806,消息为:
function
{name}has a type that is incompatible with the declaration of#[eii_name]
值得注意的是诊断细节:当差异是返回类型(TypeError::ArgumentMutability/ArgumentSorts且i恰为参数个数)时,若实现是普通同步函数,诊断会附带机器可应用(MachineApplicable)的修改建议——把返回类型替换为声明的返回类型(-> {declaration_sig.output()});当差异在某个参数上时,则建议"change the parameter type to match the declaration"。最后还通过infcx.resolve_regions解决所有区域约束,专门用来捕获生命周期参数的错误用法。
5.4 静态项的 E0806
静态项走独立的compare_eii_statics(L158-L225):用ocx.sup比较声明类型与静态项类型,失败时消息为:
static
{name}has a type that is incompatible with the declaration of#[eii_name]
并在 EII 属性处补充 note "expected this because of this attribute"。
6. 排查清单
结合文档与源码,遇到 E0806 时可按以下顺序自查:
| 检查项 | 对应源码 | 说明 |
|---|---|---|
| 属性标注在正确种类上 | check_eii_target | fn实现fn声明、static实现static声明;static还需 mutability/safety 一致 |
| 参数个数一致 | check_number_of_arguments | 诊断会直接给出 expected/found 个数 |
| 参数与返回类型一致 | ocx.sup推导 +report_eii_mismatch | 诊断常附机器可应用的类型替换建议,可直接采纳 |
| 生命周期数量一致 | check_early_region_bounds | 实现不能比声明多/少 lifetime 参数 |
| 不带泛型参数 | check_no_generics | EII 实现不支持泛型 |
| 对照标准库固定声明 | 如 panicking.rs | #[panic_handler]必须匹配fn panic_impl(pi: &PanicInfo<'_>) -> ! |
7. 小结
E0806 的本质只有一句话:带 EII 属性的项,其签名必须与其声明兼容。声明是"契约"(由标准库或#[eii]项固定),实现是"履约",编译器用参数个数、生命周期数量、泛型禁用与完整的子类型推导逐层验证契约是否成立。理解这一机制后,无论是 nightly 下实验#[eii],还是在no_std项目中编写#[panic_handler],都能在编译期立刻定位签名偏差并按诊断建议修复。
相关源码入口:
- 错误定义:compiler/rustc_error_codes/src/error_codes/E0806.md
- 检查实现:compiler/rustc_hir_analysis/src/check/compare_eii.rs
- EII 宏定义:library/core/src/macros/mod.rs
- 特性登记:compiler/rustc_feature/src/unstable.rs
- 标准库声明示例:library/core/src/panicking.rs
【免费下载链接】rustEmpowering everyone to build reliable and efficient software.项目地址: https://gitcode.com/GitHub_Trending/ru/rust
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考