首页
/ Rust 编译器历史错误码 E0447 深度解读:为什么函数内部使用 `pub` 曾经是非法的

Rust 编译器历史错误码 E0447 深度解读:为什么函数内部使用 `pub` 曾经是非法的

2026-09-07 11:53:00作者:柏廷章Berta

本篇指南以 rustc 错误码文档 E0447.md 为主线,结合当前仓库源码,完整解析“在函数内对局部 item 使用 pub”这条规则的来龙去脉:它为什么曾经是编译错误、其背后的可见性(visibility)语义依据是什么,以及它如今被归档后,Rust 开发者可以从哪些仍在产出的诊断中借鉴同一套设计思想。读完你会彻底掌握模块级可见性与函数体内 item 作用域的分界,并能在实际编码中快速判断哪些位置写 pub 是无效的。

错误码 E0447:如今已归档的历史诊断

E0447 是 rustc 早期使用过的一条编译错误码,其语义为:在函数内部对 item 使用 pub 可见性关键字是非法写法

在当前仓库中,E0447.md 的开头明确标注了它的状态:

#### Note: this error code is no longer emitted by the compiler.

也就是说,E0447 已经不再被编译器产出。rustc 采用一种“错误码存档”机制:凡是历史版本中曾经存在、但当前编译路径已不再产生的诊断码,其说明文档会保留在 compiler/rustc_error_codes/src/error_codes/ 目录下,并统一冠以“不再产出”的声明,以便开发者查询历史资料、理解旧代码,也便于对照工具链历史行为。同样的标注可以在 E0001、E0002、E0007、E0010、E0013 等同目录文档中看到,它们共同构成了错误码文档档案库。

虽然 E0447 不再被报告,但它所描述的写法背后的语言规则——函数体内声明的 item 无法被外部代码访问,因此任何可见性限定都没有意义——至今仍然成立。理解这条规则,对阅读依赖旧版工具链的代码库以及理解 Rust 的模块系统都很有帮助。

原始错误场景回顾

E0447 文档给出了触发该错误的经典代码,这是理解该诊断最直观的入口:

fn foo() {
    pub struct Bar; // error: visibility has no effect inside functions
}

在这个例子中,struct Bar 被声明在 fn foo 的函数体内,属于局部 item(local item / block-local item),而开发者又试图用 pub 将其声明为公开可见。

按照 E0447 当时的语义,编译器的态度是明确的:函数内部声明的东西,外部代码根本无法按路径访问,pub 在这里没有任何可作用的对象,因而属于无效代码,直接以硬错误(hard error)拒绝。

为什么 pub 在函数内没有意义:可见性的本质

E0447 文档中给出了规则成立的核心理由:

Since we cannot access items defined inside a function, the visibility of its items does not impact outer code. So using the pub keyword in this context is invalid.

拆解开来,这背后是 Rust 两个相互咬合的语言机制:

  1. 可见性(visibility)的作用对象是“路径可达性”pubpub(crate)pub(super) 这些限定符,回答的都是“这个 item 能否被其他模块通过路径引用”的问题。它只对挂载在模块(module)体系中的顶层 item 有意义。
  2. 函数体不是模块。函数内声明的 struct、enum、fn、const、static 等都属于块作用域(block scope),作用域被牢牢限制在包裹它的那个块/函数内部。它们既不构成模块树的节点,也无法通过 crate::super::self:: 之类的路径被函数之外的代码引用。

既然外部代码永远无法引用这个内部 item,pub 自然就退化为“无效果的关键字”,只会误导读者。这也是 E0447 将其判定为错误、而不是仅仅给出警告的根本原因。

正确写法:两条修正路径

针对文档中的错误示例,有两种合规的修正方向,取决于代码的真实意图。

路径一:item 只需要局部使用,去掉 pub

如果 Bar 只是 foo 内部的辅助类型,直接去掉可见性关键字即可:

fn foo() {
    struct Bar; // 合法:默认私有,且函数体内也不需要 pub
    let _b = Bar;
}

在函数体内,局部 item 天然对同一函数内的代码可见,不需要也不能显式声明可见性。

路径二:需要跨模块共享,把 item 提升到模块层

如果本意是让 Bar 成为模块的公开类型、供外部代码使用,那么必须把它从函数体提升到模块层级,并配合 pub

pub struct Bar; // 模块级 pub,才能真正对外可见

fn foo() {
    let _b = Bar; // 函数内依然可以使用
}

对照这两条路径可以总结出一个实用判断准则:看见一个写在函数体(或任意块)里的 pub,本质上是在提示“这段代码把作用域和可见性的层级搞混了”——要么删掉 pub,要么把 item 挪出去。

从仓库源码看错误码的归档形态

当前仓库把这类已废弃诊断的说明文档集中在 error_codes 目录。以 E0447 为例,它的文档结构是典型的“存档模板”:

  • 首行声明 no longer emitted by the compiler
  • 简述该错误码过去表达的规则(The pub keyword was used inside a function);
  • 给出当时的触发代码与报错文案;
  • 解释语言层面的判定理由。

在 rustc 内部,仍在使用的诊断码往往通过 #[diag("...", code = "EXXXX")] 属性与具体代码路径绑定,例如 ast_validation.rs 配合 diagnostics.rs 中的诊断结构体实现。当一条错误码不再被任何 #[diag(...)] 路径引用时,它就从“活跃诊断”转变为 E0447 这样的“存档文档”,留存在 error_codes 目录中供追溯。

从 E0447 延伸:现代 rustc 中“可见性无效”的同类检查

虽然 E0447 已成历史,但“这里写的可见性没有意义”这种思想在现代编译器中并未消失,而是被拆解、细化到了多个仍然产出的诊断与 lint 中。从当前仓库源码可以印证以下几个代表性实现。

1. E0449:可见性限定符在此处不允许

同样是“不可见性没有意义/不被允许”范畴,现代 rustc 对 trait 关联项、trait impl 的单个方法、外部块项、枚举变体等位置给出的是 E0449。其诊断文案定义在 diagnostics.rs

#[diag("visibility qualifiers are not permitted here", code = E0449)]
pub(crate) struct VisibilityNotPermitted { ... }

并在 ast_validation.rs 中通过 visibility_not_permitted 辅助函数对 trait impl、枚举变体、外部项等场景统一触发,还会附带解释性 note,比如“trait 项永远与其 trait 同可见性”“枚举变体及字段永远与枚举本身同可见性”。这些 note 揭示的规律与 E0447 一脉相承:当某项的可见性被其宿主强制决定、或根本不存在对外暴露路径时,再写 pub 就毫无意义。

2. unused_visibilities lint:对 const _ 上的 pub 发出警告

现代 rustc 还把一类“写了 pub 但必然无效果”的情况从硬错误降级为 warn 级别的 lint unused_visibilities。该 lint 的定义位于 builtin.rs,说明文字明确写道:

The unused_visibilities lint detects visibility qualifiers (like pub) on a const _ item... These qualifiers have no effect, as const _ items are unnameable.

对应的触发点在 ast_validation.rsItemKind::Const 分支中:当检测到 const _(下划线命名的匿名常量)携带非继承可见性时,就将该可见性 span 缓冲到 UNUSED_VISIBILITIES lint,诊断结构体 UnusedVisibility 会给出“移除限定符”的可机器应用的修改建议。其理由“const _ 不声明任何名字,因此限定符没有可作用的对象”,与 E0447 中“无法访问函数内部 item,故可见性不影响外部代码”是同一套逻辑在不同场景下的复现。

可以看到,rustc 对“无意义的可见性”采取的是按场景分级的策略:有的场合直接拒绝(E0449),有的场合降级为 lint 提示(unused_visibilities),而函数内局部 item 的 pub 这一历史场景则随语言演进退出了诊断队列(E0447 归档)。

实践建议与判断清单

对日常编写 Rust 代码的开发者,E0447 这段历史可以浓缩为如下几条可操作的判断准则:

  1. 见到块内的 pub,先问访问路径:凡是在 fnloopif、闭包等块作用域里声明的 item,外部代码一律无法按路径引用,pub 必然无效。
  2. 想公开,先上移作用域:把需要被外部引用的类型/常量/函数移到模块层级(或对应的 mod 里),再附上恰当的 pubpub(crate)pub(super)
  3. 只想局部用,就删掉限定符:函数体内的辅助类型直接声明即可,无需任何可见性关键字。
  4. 留意提示而非只盯着错误:即使某条代码不再触发 E0447,也可能触发 E0449 硬错误,或触发 unused_visibilities 这类 lint,编译器的提示信息会直接告诉你该删除限定符还是调整声明位置。

小结

E0447 是理解 Rust 可见性模型的一扇绝佳窗口。它记录的“函数内 pub 无效”规则,本质是模块系统与块作用域两条路径规则的交叉结论。当前它虽已归档于 error_codes 目录、不再被编译器产出,但其背后的判定哲学仍然活跃在 E0449 与 unused_visibilities 等现代诊断中。深入理解这类历史错误码的“为什么”,远比记住“删掉 pub 就好”的机械结论更有价值。

登录后查看全文
热门项目推荐
相关项目推荐