Rust 编译器错误 E0365 深度解析:私有模块的公开重导出与可见性修复
【免费下载链接】rustEmpowering everyone to build reliable and efficient software.项目地址: https://gitcode.com/GitHub_Trending/ru/rust
导读
E0365 是 rustc 在解析阶段(rustc_resolve)针对"私有模块被pub use公开重导出"这一可见性冲突产生的编译错误。本文以本仓库中官方错误文档 E0365.md 为主线,结合编译器解析源码与 ui 测试用例,讲清该错误的触发场景、两种典型报错文案、pub mod/受限可见性两类修复思路,以及它与相邻错误 E0364 的边界,帮助你理解 Rust 模块系统与可见性模型的底层判定逻辑。
E0365 是什么:一句话速览
该错误码的官方说明为:"Private modules cannot be publicly re-exported."——即你通过pub use重导出的模块自身并不是pub的。
它属于编译期硬错误(hard error),在名称解析(name resolution)阶段即被报告,而不是 lint 警告或运行时问题。想要对外部用户可见,被重导出的条目本身必须具备足以支撑该重导出的可见性,否则 rustc 会直接拒绝生成二进制或库产物。
触发 E0365 的最小复现示例
E0365.md 中给出的错误示例是模块中包含pub条目、但模块自身为私有的场景:
mod foo { pub const X: u32 = 1; } pub use foo as foo2; fn main() {}这里foo模块本身未声明pub,尽管其中的常量X是pub的,尝试对模块本身执行pub use foo as foo2依然会触发 E0365。这与 E0365.md 的语义定位一致:重导出对象是"模块"这一类型命名空间中的条目,判断依据是被导出者自身的可见性。
该示例同时也是仓库中回归测试文件 tests/ui/error-codes/E0365.rs 的主体,保证真实编译行为与文档描述同步。
错误输出的真实形态:两种 E0365 文案
通过 tests/ui/error-codes/E0365.stderr 可以看到上述代码实际产生的完整诊断:
error[E0365]: `foo` is only public within the crate, and cannot be re-exported outside --> E0365.rs:5:9 | LL | pub use foo as foo2; | ^^^^^^^^^^^ re-export of crate public `foo` | = note: consider declaring type or module `foo` with `pub`注意,报错文案并非文档中概括的 "is private",而是"is only public within the crate"。这说明 rustc 对可见性做了更细的建模,E0365 在诊断层面对应两种消息:
`{$ident}` is private, and cannot be re-exported——针对完全私有的模块;`{$ident}` is only public within the crate, and cannot be re-exported outside——针对仅 crate 内公开(相当于pub(crate)语义)的模块。
这两条消息分别由诊断结构体CannotBeReexportedPrivateNS与CannotBeReexportedCratePublicNS定义,可在 compiler/rustc_resolve/src/diagnostics/mod.rs#L804-L830 中看到(含代码 E0365 与修复提示 note、以及re-export of private/crate public ...的 span 标注):
#[derive(Diagnostic)] #[diag("`{$ident}` is private, and cannot be re-exported", code = E0365)] #[note("consider declaring type or module `{$ident}` with `pub`")] pub(crate) struct CannotBeReexportedPrivateNS { ... } #[derive(Diagnostic)] #[diag("`{$ident}` is only public within the crate, and cannot be re-exported outside", code = E0365)] #[note("consider declaring type or module `{$ident}` with `pub`")] pub(crate) struct CannotBeReexportedCratePublicNS { ... }这也解释了为何文档中的顶层示例会命中"crate public"分支:位于 crate 顶层(is_top_level_module())的私有模块,在 rustc 的可见性模型中被视作restricted 到 crate 自身的可见性(详见下文"源码级判定逻辑")。
何时报 "is private" 而非 "only public within the crate"?
判定来自 compiler/rustc_resolve/src/imports.rs#L1668-L1671:
let crate_private_reexport = match decl.vis() { Visibility::Restricted(mod_id) if mod_id.is_top_level_module() => true, _ => false, };当被重导出模块的可见性被建模为"restricted 到顶层模块"时按 crate-public 处理(输出 "only public within the crate");其他受限情形(例如嵌套在私有父模块下、受限到某个具体模块)则输出 "is private"。因此嵌套于私有模块内部的pub use通常命中 private 分支,而直接声明在 crate 根下、仅未写pub的顶层模块通常命中 crate-public 分支。
修复方法:让模块本身公开(官方推荐方案)
E0365.md 给出的标准修法非常直接——把被重导出的模块声明为pub:
pub mod foo { pub const X: u32 = 1; } pub use foo as foo2; fn main() {}由于foo现在对 crate 外部可见,pub use foo as foo2的重导出链合法成立,编译通过。rustc 的诊断输出中也会给出同义建议:note: consider declaring type or modulefoowithpub``。
修复的变体:为不同可见性场景选择合适写法
在真实项目中,修复方案并不只有"加pub"一种,需依据你对模块的实际可见性意图来选择:
- 模块需对下游使用者公开,并希望以别名重导出——将模块声明为
pub mod foo后再pub use foo as foo2(即上述方案); - 模块仅在当前 crate 内部复用、不打算对外暴露——可把重导出连同模块本身都降为 crate 内可见,例如
pub(crate) use foo as foo2,此时即便foo是pub(crate)/私有模块也不会报 E0365,因为重导出的名义可见性并未超出被导出者的可见性; - 只想让模块中的某个
pub条目对外可见——不要重导出整个模块,改为直接pub use foo::X,并保持mod foo私有(模块私有不阻止其内部pub条目被单独重导出,前提是这些条目可达)。
说明:E0365 触发的关键是比较"重导出语句的可见性"与"被导出条目的可见性"。若重导出方的名义可见性严格大于被导出条目(例如
pub重导出pub(crate)/私有模块),即构成非法 re-export。这条比较逻辑见 imports.rs#L1619-L1634。
源码级原理:E0365 是在哪一步、如何被抛出的
E0365 的产生地是rustc_resolve的名称解析对 import 的处理过程。可以从 compiler/rustc_resolve/src/imports.rs 的核心流程看到完整判定链条:
- 解析
pub use时,编译器按命名空间分别取得目标 binding(self.per_ns(...)); - 逐命名空间比较
import.vis与被导入项binding.vis()的可见性大小:当import.vis.greater_than(binding.vis(), this.tcx)为真,说明该重导出"越权",记录为 re-export error; - 只有当所有由该 import 引入的声明都比 import 的名义可见性更私有时,才真正发射错误(对应注释 "In isolation, a declaration like this is not an error, but ifall1-3 declarations introduced by the import are more private than the import item's nominal visibility, then it's an error.",见 imports.rs#L1620-L1634);
- 实际发射由
report_cannot_reexport(imports.rs#L1661)完成:当命中TypeNS(类型命名空间,模块即归属于此)时创建带code = E0365的诊断结构体,并调用err.emit()输出。
E0364 与 E0365 如何分工
看到上面第 4 步你可能已经留意到:同一段 re-export 可见性检查逻辑还同时负责产生相邻错误码E0364。两者的分工在 imports.rs#L1684-L1696 中体现:
- 命中TypeNS(典型的如模块、非泛型类型别名场景)→ 创建
CannotBeReexported*NS变体,code = E0365,即本文主角; - 命中其他命名空间(值/宏等)→ 创建
CannotBeReexportedPrivate/CannotBeReexportedCratePublic,code = E0364。
两份官方文档相互印证:
- E0364.md:Privateitemscannot be publicly re-exported(针对类型/值等一般条目),其示例是
pub use super::foo重导出私有函数fn foo(); - E0365.md:Privatemodulescannot be publicly re-exported(针对模块)。
因此,调试时若报 E0364,去检查被重导出的函数、常量、类型等条目自身的可见性;若报 E0365,则优先检查被重导出的模块是否漏写了pub。
补充:私有extern crate被pub use的关联处理
E0365 家族还有一个特殊场景:通过extern crate引入的 crate 本身是私有的,却被pub use重导出。在 imports.rs#L1673-L1683 中,rustc 先通过pub_use_of_private_extern_crate_hack识别该情况,构造PrivateExternCrateReexport诊断(同样携带code = E0365,定义于 diagnostics/mod.rs#L833-L843),其建议为在被引用的extern crate前补pub:
consider making the `extern crate` item publicly accessible不过这条路径走的是**提前 lint(buffered early lint)**通道(lint 名pub_use_of_private_extern_crate,见 imports.rs#L1678-L1683),与该错误码常规的err.emit()直发路径不同,属于历史遗留兼容处理。
为什么 Rust 要禁止私有模块被公开重导出
这条规则本质上是Rust 可见性边界(privacy boundary)的必然推论:
- 模块系统的意义之一就是封装:非
pub模块内部的条目细节对下游不可见; pub use的语义是"把已可见的路径用另一个名字重新暴露给(可能更广的)使用者"。如果允许把私有模块直接提升到对外可见,就相当于绕过pub的显式声明偷偷扩大 API 面,使库的公开接口取决于"内部实现细节是否被顺手重导出",破坏最小公开接口(minimum public surface)的可控性;- rustc 在解析阶段用可见性偏序比较(
vis.greater_than)强制执行该边界,让"先声明pub再重导出"成为唯一合规路径。
从 API 设计角度,这也是一条实用建议:库 crate 对外暴露任何路径前,都应检查链路上每个中间模块与条目的可见性是否都至少等于该路径的公开程度,避免在重构模块树时被 E0364/E0365 这类"可见性传递中断"问题反复打断。
深入阅读
- 官方错误码文档本体:compiler/rustc_error_codes/src/error_codes/E0365.md 与相邻错误 E0364.md
- 错误码在
rustc_error_codes中的注册表项:compiler/rustc_error_codes/src/lib.rs#L196 - 诊断消息结构体(两种 E0365 文案、修复 note 与代码定位标注):compiler/rustc_resolve/src/diagnostics/mod.rs#L796-L843
- 触发逻辑(命名空间分工、可见性偏序比较、early lint 分支):compiler/rustc_resolve/src/imports.rs#L1612-L1709
- 编译测试与期望输出(用于验证本文所有示例的编译器实际行为):tests/ui/error-codes/E0365.rs、tests/ui/error-codes/E0365.stderr
若你在本地构建过本仓库的 rustc,可直接用rustc --explain E0365查看内置的该错误说明,其内容与上述官方文档一致。
【免费下载链接】rustEmpowering everyone to build reliable and efficient software.项目地址: https://gitcode.com/GitHub_Trending/ru/rust
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考