Rust 编译器错误码 E0137 解析:程序必须只有一个入口函数

Rust 编译器错误码 E0137 解析:程序必须只有一个入口函数
Rust 编译器错误码 E0137 解析程序必须只有一个入口函数【免费下载链接】rustEmpowering everyone to build reliable and efficient software.项目地址: https://gitcode.com/GitHub_Trending/ru/rustRust 编译器要求每个可执行程序必须有且仅有一个入口函数错误码 E0137 正是为“同时声明了多个入口函数”这一类错误而生的诊断。本篇基于 Rust 仓库中的错误码文档 E0137.md 展开结合当前源码入口点解析逻辑rustc_passes/src/entry.rs与诊断定义rustc_passes/src/diagnostics.rs讲清 E0137 的历史语义、为何标记为“不再发出”以及它在当前编译器中对应的#[rustc_main]检查实现。一、E0137 的原始语义多个函数声明了#[main]错误码文档 E0137.md 开篇即标注Note: this error code is no longer emitted by the compiler.这句话是理解整个错误码的关键前提。E0137 原本对应的场景是使用不稳定特性main#![feature(main)]为程序入口函数改名例如把main写成foo。该特性允许开发者用#[main]属性标记任意函数作为程序入口// 文档中的历史示例特性 main 已移除此代码无法再编译 #![feature(main)] #[main] fn foo() {}而错误发生的原因是一个 Rust 程序只能有唯一的入口点。如果同时声明了多个带#[main]属性的函数编译器就无法确定程序应该从哪一个开始执行。原文档给出的报错示例正是这种情况#![feature(main)] #[main] fn foo() {} #[main] fn f() {} // error: multiple functions with a #[main] attribute编译器会对其中第二个声明报出multiple functions with a #[main] attribute错误。文档同时给出合法用法——只声明一个带#[main]属性的函数#![feature(main)] #[main] fn f() {} // ok!从源码结构看main这个不稳定特性以及#[main]属性已从当前编译器中移除因此文档中的两个compile_fail示例在今天的新工具链上已经不能按原样复现这就是“no longer emitted”的直接原因。二、为什么入口点必须唯一E0137 背后的设计原则是一个 Rust 程序的执行入口必须是唯一的。这与语言整体结构一致——可执行程序crate type 为bin由链接器寻找唯一的入口符号开始执行编译器在入口点解析阶段遍历所有顶层函数寻找符合入口条件的函数如果找到多个候选历史上是多个#[main]当前对应的是内部属性#[rustc_main]诊断立即报出不允许编译器“猜”一个。当前编译器中入口点的判定逻辑集中在 compiler/rustc_ast/src/entry.rs。其中EntryPointType枚举定义了四种函数分类pub enum EntryPointType { /// This function is not an entrypoint. None, /// This is a function called main at the root level. MainNamed, /// This is a function with the #[rustc_main] attribute. /// Used by the testing harness to create the test entrypoint. RustcMainAttr, /// This function is **not** an entrypoint but simply named main (not at the root). /// This is only used for diagnostics. OtherMain, }可以看到取代旧#[main]属性的正是#[rustc_main]属性而它的定位是内部用途由测试框架test harness在生成测试入口时标记真正的入口函数。entry_point_type函数的判定优先级为只要函数带#[rustc_main]属性即归入RustcMainAttr否则名为main且位于 crate 顶层的归入MainNamed名为main但不在顶层的比如写在子模块里归入OtherMain仅用于改善“找不到 main”时的诊断提示。三、当前编译器中 E0137 的实际触发路径虽然历史场景失效但 E0137 这个错误码并没有被废弃——它被重新绑定到了#[rustc_main]的重叠检查上。3.1 诊断定义在 compiler/rustc_passes/src/diagnostics.rs 中MultipleRustcMain诊断直接绑定了code E0137#[derive(Diagnostic)] #[diag(multiple functions with a #[rustc_main] attribute, code E0137)] pub(crate) struct MultipleRustcMain { #[primary_span] pub span: Span, #[label(first #[rustc_main] function)] pub first: Span, #[label(additional #[rustc_main] function)] pub additional: Span, }诊断文案与历史版本一脉相承从#[main]变为#[rustc_main]并且带两个标签分别指向“第一个”和“多余的”入口函数方便开发者快速定位冲突。3.2 检查逻辑entry_fn 查询实际发出该错误的代码位于 compiler/rustc_passes/src/entry.rs。入口点解析是编译器的一个 queryentry_fn其流程为前置短路若 crate 不是可执行类型或带了#![no_main]直接返回None不做入口检查见 entry.rs 中的entry_fn函数遍历顶层项for id in tcx.hir_free_items()对每个顶层项调用check_and_search_item记录第一个、拒绝第二个EntryPointType::RustcMainAttr { if ctxt.rustc_main_fn.is_none() { ctxt.rustc_main_fn Some((id.owner_id.def_id, ctxt.tcx.def_span(id.owner_id))); } else { ctxt.tcx.dcx().emit_err(MultipleRustcMain { span: ctxt.tcx.def_span(id.owner_id.to_def_id()), first: ctxt.rustc_main_fn.unwrap().1, additional: ctxt.tcx.def_span(id.owner_id.to_def_id()), }); } }第一个带#[rustc_main]的函数被记录为候选入口第二个出现时立即通过MultipleRustcMain报出 E0137并在最终configure_main中该属性函数会优先于名字解析resolver结果成为入口见 entry.rs 的configure_main。3.3#[rustc_main]属性本身属性的解析定义在 compiler/rustc_attr_parsing/src/attributes/rustc_internal.rspub(crate) struct RustcMainParser; impl NoArgsAttributeParser for RustcMainParser { const PATH: [Symbol] [sym::rustc_main]; const ALLOWED_TARGETS: AllowedTargets_ AllowedTargets::AllowList([Allow(Target::Fn)]); const STABILITY: AttributeStability unstable!( rustc_attrs, the rustc_main attribute is used internally to specify test entry point function ); const CREATE: fn(Span) - AttributeKind |_| AttributeKind::RustcMain; }几个要点该属性挂在rustc_attrs不稳定特性下属于编译器内部机制稳定代码不应使用只允许标注在函数Target::Fn上且不接受任何参数NoArgsAttributeParser主要使用者是测试框架compiler/rustc_builtin_macros/src/test_harness.rs 在生成--test模式的入口时会查找/移除用户自带的#[rustc_main]属性相关逻辑见该文件第 179、202–216 行附近并在需要时注入#[rustc_main]标记由它生成的测试入口。因此普通开发者日常几乎不会直接撞见 E0137它主要出现在编写编译器测试基础设施、或手动滥用rustc_attrs特性的场景。四、E0137 与相邻错误码的关系理解 E0137 最好把它放进“入口点诊断族”中看同一文件entry.rs还负责其他入口相关诊断错误码诊断结构触发场景E0137MultipleRustcMain多个函数声明了#[rustc_main]历史上是#[main]入口不唯一E0601NoMainErr可执行 crate 中完全找不到main函数诊断中会借助non_main_fns如子模块里误写的main给出提示—无编号ExternMain入口main被声明在extern块中报the main function cannot be declared in an extern block三者共同保证了入口点解析的完备性要么恰好一个合法入口MainNamed或RustcMainAttr要么报出明确的错误并附上可操作的提示。五、错误码文档的维护机制E0137 文档的“不再发出”标注并不是随手写的而是 Rust 仓库对错误码文档有一套强制维护流程实现在 compiler/rustc_error_codes/src/lib.rs所有在用的错误码集中声明在error_codes!宏列表中0137位于该列表内见 lib.rs该列表被rustc_errorscrate 使用并被 tidy 工具校验文件头部注释明确规定不允许从列表中删除已存在的错误码条目即使编译器不再发出该错误也应保留条目并在对应的 Markdown 文件如本例 E0137.md中注明“该错误不再由编译器发出”同时把无法再构建的代码示例标记为ignore (no longer emitted)文档需遵循 RFC 1567 对长错误码说明的规范化要求。E0137 正是这一流程的典型案例main特性移除后文档保留、示例留档、错误码转绑到#[rustc_main]检查保证了错误码编号空间的稳定性与文档的可追溯性——这也是为什么今天阅读 E0137 文档时会同时看到“历史用法”和“不再发出”两种信息。六、小结E0137 的核心语义是“程序入口必须唯一”历史上由多个#[main]属性函数触发如今由多个#[rustc_main]属性函数触发原始#![feature(main)]用法已随特性移除而失效文档中据此标注 no longer emitted但错误码本身通过MultipleRustcMain诊断继续服役入口点解析是rustc_passes中的entry_fnquery先按 crate 类型与#![no_main]短路再遍历顶层项分类EntryPointType#[rustc_main]优先于按名解析的main对普通 Rust 用户而言避免入口问题的实践很简单确保 crate 顶层恰好有一个fn main把非入口的同名函数挪出顶层或在确实不需要入口时显式使用#![no_main]常用于 no_std 或裸平台目标。【免费下载链接】rustEmpowering everyone to build reliable and efficient software.项目地址: https://gitcode.com/GitHub_Trending/ru/rust创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

最新新闻

日新闻

周新闻

月新闻