Rust E0577 深度解析:为什么 pub(in path) 可见性路径中出现了非模块
【免费下载链接】rustEmpowering everyone to build reliable and efficient software.项目地址: https://gitcode.com/GitHub_Trending/ru/rust
在 Rust 中使用 2018 及以后版本的pub(in path)可见性语法时,如果路径解析到的目标不是模块(module),编译器会报出 E0577 错误(expected module, found ...)。本文围绕官方错误码文档 E0577.md 展开,先完整呈现官方文档中的错误示例与修复方式,再深入 rustc 名称解析(resolve)阶段的源码,说明该错误是在哪个环节、依据什么规则被触发的,并梳理与 E0578、E0742 等相邻可见性错误的区别与适用前提,帮助你在实际开发中快速定位和修复这类可见性配置错误。
E0577 错误码是什么
E0577 的官方定义标题是:Something other than a module was found in visibility scope(在可见性作用域中找到了非模块)。
在 2018 edition 引入的可见性限定语法中,pub(in path)要求path最终必须解析到一个模块。编译器在 resolve 阶段为该错误生成的诊断消息是:
expected module, found {$res} `{$path_str}`并附带not a module的标注。这一诊断结构定义在 diagnostics/mod.rs 中:
#[derive(Diagnostic)] #[diag("expected module, found {$res} `{$path_str}`", code = E0577)] pub(crate) struct ExpectedModuleFound { #[primary_span] #[label("not a module")] pub(crate) span: Span, pub(crate) res: Res, pub(crate) path_str: String, }其中res是实际解析到的定义种类(enum、struct、fn 等),path_str是你写下的可见性路径字符串。错误消息中 "found enum" 或 "found struct" 这类措辞,就是由{$res}格式化而来。
官方文档的完整示例:错误与修复
官方文档 E0577.md 给出的最小复现代码如下(edition2018,预期编译失败):
pub enum Sea {} pub (in crate::Sea) struct Shark; // error! fn main() {}这里Sea是一个枚举(enum),而不是模块,因此把它用进pub(in crate::Sea)的可见性路径是非法的。编译器会指出crate::Sea这个路径 "expected module, found enumSea"。
修复方式与官方文档一致——确保可见性路径中的每一项都是模块:
pub mod sea { pub (in crate::sea) struct Shark; // ok! } fn main() {}把Sea改成模块sea后,crate::sea解析到的是一个真正的模块,Shark的可见性被合法地限定为"在crate::sea模块内可见"。
官方文档还特别强调了一点:可见性作用域只能应用到祖先模块上(the visibility scope can only be applied on ancestors)。也就是说,pub(in path)中的模块必须是当前项的父级模块(crate 根或逐级嵌套的祖先),不能是平级或下级模块——违反这条规则会报 E0742(visibilities can only be restricted to ancestor modules),而不是 E0577。
源码级剖析:E0577 是在哪一步触发的
E0577 由 rustc 的 resolve 阶段(crate 级名称解析)发出。追踪源码调用链,可以看到完整的触发路径:
可见性路径的解析入口
可见性路径的解析发生在构建"reduced graph"(降低后的解析图)期间,核心代码位于 build_reduced_graph.rs。其中有一段关键的版本(edition)处理逻辑:
let ident = path.segments.get(0).expect("empty path in visibility").ident; let crate_root = if ident.is_path_segment_keyword() { None } else if ident.span.is_rust_2015() { Some(Segment::from_ident(Ident::new( kw::PathRoot, path.span.shrink_to_lo().with_ctxt(ident.span.ctxt()), ))) } else { return Err(VisResolutionError::Relative2018( ident.span, path.as_ref().clone(), )); };从源码结构看,这段逻辑可以推断出两条重要规则:
- 路径首段必须是关键字段(
crate、self、super等)。否则在 2015 edition 下编译器会自动补一个 crate 根(即crate::前缀),而在 2018 edition 及以后直接报Relative2018错误——"relative paths are not supported in visibilities in 2018 edition or later",并建议改写为crate::{path}。 - 路径解析通过
resolve_path完成;如果解析成功但目标不是一个模块,就走下面定义的expected_found_error闭包:
let expected_found_error = |res| { Err(VisResolutionError::ExpectedFound( path.span, Segment::names_to_string(&segments), res, )) };VisResolutionError::ExpectedFound(span, path_str, res)正是携带"路径 span、路径字符串、实际解析结果"三元组的错误变体——与诊断结构ExpectedModuleFound的字段一一对应。
错误上报:report_vis_error
所有可见性解析错误最终统一由 diagnostics/impls.rs 中的report_vis_error分发处理:
VisResolutionError::ExpectedFound(span, path_str, res) => { self.dcx().create_err(diagnostics::ExpectedModuleFound { span, res, path_str }) }这一行即 E0577 的最终出口。同函数中还能看到可见性错误家族的其余成员,便于区分:
| 变体 | 错误码 | 消息 | 触发条件 |
|---|---|---|---|
Relative2018 | —(建议型诊断) | relative paths are not supported in visibilities in 2018 edition or later | 2018+ 下可见性路径以非关键字开头 |
AncestorOnly | E0742 | visibilities can only be restricted to ancestor modules | 可见性指向的不是祖先模块 |
ExpectedFound | E0577 | expected module, found{$res}{$path} | 路径解析成功但目标不是模块 |
Indeterminate | E0578 | cannot determine resolution for the visibility | 可见性路径无法确定解析 |
ModuleOnly | — | visibility must resolve to a module | 可见性必须解析到模块 |
注意一个容易混淆的边界:路径根本不存在(拼错模块名)报的是 E0433 一类的"找不到路径"错误;路径存在但解析到非模块(enum、struct、fn 等)才报 E0577。
E0577 的复用:restrictions 场景
值得注意的一点是,E0577 并非只服务于pub(in path)。在 late.rs 的路径来源(PathSource)错误码映射中可以看到:
// FIXME: There is no dedicated error code for this case yet. // E0577 already covers the same situation for visibilities, // so we reuse it here for now. It may make sense to generalize // it for restrictions in the future. (PathSource::Module, true) => E0577, (PathSource::Module, false) => E0433,源码注释明确说明:对于"期望是模块但发现了其他东西"的 restriction 场景(例如精确捕获use项时用到的crate::...限定),编译器暂时复用了 E0577。因此看到 E0577 时,可以确认报错位置"期望出现模块的地方出现了非模块项"这一语义是稳定的,但具体来源可能是可见性路径,也可能是 restriction 路径。
如何避免与修复 E0577
结合官方文档与源码规则,可以归纳出以下检查清单:
- 逐项确认路径上每一段都是模块。
pub(in crate::a::b)要求crate::a和a::b都是模块。若某一段是 enum、struct、trait 等,就会触发expected_found_error,报出 E0577。 - 2018 edition 起路径必须以关键字开头。可见性路径不能写成相对路径(如
pub(in sea) ...),必须写成crate::sea;2015 edition 则会自动按 crate 根解析。 - 可见性目标必须是祖先模块。
pub(in crate::foo)只能用于foo内部或其更深层的项。对平级模块使用会报 E0742。 - 区分 E0577 与 E0578:E0578(
cannot determine resolution for the visibility)表示可见性路径无法确定解析结果,例如路径段落在多个命名空间中产生歧义;而 E0577 表示路径已解析成功,但目标不是模块。
用官方错误文档示例做本地验证
官方文档中的示例采用 rustc 测试注解格式(compile_fail,E0577,edition2018),可以直接作为手工复现用例。把错误示例保存为独立文件后,用 rustc 按 2018 edition 编译即可看到诊断输出:
rustc --edition=2018 error.rs预期输出形如:
error: expected module, found enum `Sea` --> error.rs:3:10 | 3 | pub (in crate::Sea) struct Shark; // error! | ^^^^^^^^ not a module | = note: see the Error... E0577修复后(将Sea改为pub mod sea { ... })同一命令应无任何报错。这一"预期失败 → 修复 → 编译通过"的验证闭环,也正是仓库中 UI 测试以.rs测试文件加.stderr预期输出文件的形式组织的原因(参见 tests/ui 目录)。
小结
- E0577 的语义是"期望模块,却发现了非模块项",在
pub(in path)可见性语法中最常遇到,其诊断结构ExpectedModuleFound定义于 diagnostics/mod.rs。 - 触发点位于 resolve 阶段:build_reduced_graph.rs 负责可见性路径的解析与 edition 规则检查,diagnostics/impls.rs 的
report_vis_error负责最终上报。 - 修复思路只有一个方向:让可见性路径的每一段都落在真正的模块上,且该模块是当前项的祖先模块;2018 edition 及以后还必须以
crate/self/super等关键字开头。 - E0577 还与 E0578(可见性无法确定解析)、E0742(非祖先模块)同属可见性错误家族,三者的区分关键在于"解析成功与否"以及"目标是否为祖先模块"。
理解这一错误码的完整链路——从语法约束、resolve 阶段的解析逻辑到诊断消息的生成——能让你在遇到expected module, found ...报错时,不必反复试错,直接对照上述规则定位是路径段写错了种类、版本规则不匹配,还是祖先约束被违反。
【免费下载链接】rustEmpowering everyone to build reliable and efficient software.项目地址: https://gitcode.com/GitHub_Trending/ru/rust
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考