Rust 错误 E0603:function, struct or module is private(私有项)
E0603 其实是个披着坏消息外衣的好消息:Rust 找到了你要的函数、结构体或模块,只是不能交给你,因为它是私有的。Rust 模块里的一切,在你明确说明之前都是私有的,所以几乎每个人第一次把代码拆成模块时,都会遇到这个错误。修法通常只要一个词:pub。这一页属于 Rust 编译错误详解系列。
error[E0603]: function `apply_discount` is private --> src/main.rs:8:26 |8 | let total = billing::apply_discount(200); // ❌ 私有的 | ^^^^^^^^^^^^^^ private function |note: the function `apply_discount` is defined here --> src/main.rs:2:5 |2 | fn apply_discount(price: u32) -> u32 { // 没有 `pub` | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^第一行的意思是“函数 apply_discount 是私有的”,note 则告诉你这个函数定义在哪里。
产生这个错误的程序是这样的:
mod billing { fn apply_discount(price: u32) -> u32 { // 没有 `pub` price * 90 / 100 }}
fn main() { let total = billing::apply_discount(200); // ❌ 私有的 println!("总计:{total}");}第一行会写出是哪一种项,所以同一个错误有好几种样子:
| 第一行写的是 | 通常哪里出了问题 | 修法 |
|---|---|---|
function `apply_discount` is private |
这个 fn 没有 pub |
修复 1 或修复 2 |
struct `Order` is private |
这个 struct 没有 pub |
修复 1 |
module `prices` is private |
路径上的某个 mod 没有 pub |
修复 1 或修复 3 |
function import `price_of` is private |
你通过另一个模块里的普通 use 去访问一个项 |
修复 3 |
tuple struct constructor `Percent` is private |
结构体是 pub 的,但它的字段不是 |
修复 4 |
这个错误是什么意思
Section titled “这个错误是什么意思”看那两个箭头。第一个(第 8 行)是你使用这个项的地方,note: 的箭头(第 2 行)是这个项定义的地方。Rust 在说:“我看到那边有个 apply_discount,但它从来没有被标记为公开,所以 billing 外面的代码不能调用它。”
和文件无关。如果 billing 放在 src/billing.rs 里,你会得到同样的错误,只是 note: 改为指向 src/billing.rs:1:1。
想象一栋办公楼。每个模块都是一间有门的房间。私有项就是留在房间里的工具:房间里的人(以及房间里套着的小房间里的人)都能用,但走廊里的人用不了。pub 就是把工具放到走廊的架子上。
Rust 为什么要这样做
Section titled “Rust 为什么要这样做”Rust 里的一切默认都是私有的:函数、结构体、结构体字段、模块,甚至 use 导入。想共享,就用 pub 主动声明。模块这一课一开始就讲了这一点。
这样做是为了保护将来的你。凡是 pub 的东西,都相当于一个承诺:别的代码可能依赖它,所以改动它可能会破坏那些代码。私有项则可以随意修改、改名或删除,编译器保证外面没有任何代码依赖它们。小辅助函数始终只是辅助函数;结构体也能守住自己的规则(比如“余额永远不会是负数”),因为别人没法直接碰它的字段。
还有一个细节:路径的每一步都会检查可见性。shop::prices::price_of 只有在 prices 对你可见、并且 price_of 也对你可见时才能用。私有模块里的 pub fn 照样够不着。
修复 1:给这个项(以及路径上的每个模块)加上 pub
Section titled “修复 1:给这个项(以及路径上的每个模块)加上 pub”当这个项本来就是要给模块外面用的时,选这个方法。大多数时候这都是正确的修法:
mod billing { pub fn apply_discount(price: u32) -> u32 { price * 90 / 100 }}
fn main() { let total = billing::apply_discount(200); println!("总计:{total}");}输出:
总计:180如果错误说 module `prices` is private,缺的 pub 在模块上:写成 pub mod prices { ... }。对于这种情况,rustc 还会用第二个标签提示你:function `price_of` is not publicly re-exported(函数 price_of 没有被公开地重新导出)。如果模块在单独的文件里,pub 要加在声明那一行:pub mod prices;。
对于结构体,pub struct Order 只是让这个类型公开,如果外面的代码要读写字段,每个字段还得单独加 pub。修复 4 里会细说。
修复 2:用 pub(crate) 或 pub(super) 表示“只给我自己的代码用”
Section titled “修复 2:用 pub(crate) 或 pub(super) 表示“只给我自己的代码用””当这个项需要共享,但不想对全世界公开时,选这个方法。pub 有好几个级别,从小到大:
| 你写的是 | 谁能使用这个项 |
|---|---|
| 什么都不写(私有) | 它所在的模块,以及其中的子模块 |
pub(super) |
再加上父模块 |
pub(crate) |
这个 crate 里的任何代码 |
pub |
所有人,包括依赖你的其他 crate |
在一个程序(二进制 crate)里,pub(crate) 和 pub 几乎没有区别。区别在库里才显现:pub(crate) 让你的各个模块可以共享一个辅助函数,同时又不把它放进库的公开 API。
mod shop { // 这个 crate 里的任何代码都能调用它。 pub(crate) fn store_name() -> &'static str { "Ferris Fruits" }
pub mod receipt { // 只有父模块 `shop` 能调用这个辅助函数。 pub(super) fn line(item: &str, price: u32) -> String { format!("{item}: {price}") } }
pub fn print_receipt(items: &[(&str, u32)]) { println!("== {} ==", store_name()); for (item, price) in items { println!("{}", receipt::line(item, *price)); } }}
fn main() { println!("欢迎光临 {}", shop::store_name()); shop::print_receipt(&[("apple", 3), ("pear", 5)]); // shop::receipt::line("apple", 3); // ❌ E0603:`line` 只给 `shop` 用}输出:
欢迎光临 Ferris Fruits== Ferris Fruits ==apple: 3pear: 5把最后一行前面的 // 去掉,你就会得到 function `line` is private,尽管它写着 pub(super)。main 不是 receipt 的父模块,所以门还是关着的。
修复 3:用 pub use 重新导出
Section titled “修复 3:用 pub use 重新导出”当内部模块只是实现细节,但里面某一个项应该方便访问时,选这个方法。让模块保持私有,把这个项放到一条更短的公开路径上:
mod shop { mod prices { // 保持私有:价格怎么存是 shop 自己的事 pub fn price_of(item: &str) -> u32 { if item == "apple" { 3 } else { 5 } } }
pub use prices::price_of; // 现在可以通过 shop::price_of 访问}
fn main() { println!("苹果的价格:{}", shop::price_of("apple")); println!("梨的价格:{}", shop::price_of("pear"));}输出:
苹果的价格:3梨的价格:5这也是 function import `price_of` is private 的修法。在 shop 里写一个普通的 use prices::price_of;,只是给 shop 自己用的快捷方式;和其他东西一样,use 也是私有的。pub use 把这个快捷方式变成一扇公开的门。大型 crate 一直这么做,所以你可以直接写 serde::Serialize,而不用知道它到底在哪个内部模块里。
修复 4:加一个 pub fn new,而不是把字段公开
Section titled “修复 4:加一个 pub fn new,而不是把字段公开”当错误和结构体的字段或构造函数有关时,选这个方法。下面这个结构体是 pub 的,但它的字段不是:
mod billing { pub struct Percent(u8);}
fn main() { let discount = billing::Percent(150); // ❌ 私有的构造函数}error[E0603]: tuple struct constructor `Percent` is private --> src/main.rs:6:29 |2 | pub struct Percent(u8); | -- a constructor is private if any of the fields is private...6 | let discount = billing::Percent(150); // ❌ 私有的构造函数 | ^^^^^^^ private tuple struct constructor |help: consider making the field publicly accessible |2 | pub struct Percent(pub u8); | +++这里的意思是:只要有任何一个字段是私有的,构造函数就是私有的。rustc 建议写成 pub u8,对于普通的数据类型,这样没问题。但看看这个值:150%!很多时候,私有字段正是关键所在:模块想在 Percent 被创建之前先检查这个值。那就给它一个负责检查的公开构造函数:
mod billing { pub struct Percent(u8);
impl Percent { pub fn new(value: u8) -> Option<Percent> { if value <= 100 { Some(Percent(value)) } else { None } }
pub fn apply(&self, price: u32) -> u32 { price * (100 - self.0 as u32) / 100 } }}
fn main() { let ten = billing::Percent::new(10).unwrap(); println!("200 打九折(减 10%)后:{}", ten.apply(200)); println!("允许 150% 吗?{}", billing::Percent::new(150).is_some());}输出:
200 打九折(减 10%)后:180允许 150% 吗?false现在整个程序里的每一个 Percent 都保证在 0 到 100 之间,apply 永远不会下溢。别忘了 new 本身也需要 pub;私有方法会给出另一个错误 E0624(associated function `new` is private)。关于构造函数的更多内容,见结构体。
一个包里同时有 main.rs 和 lib.rs。 它们是两个独立的 crate,main.rs 是从外面使用这个库的。如果 lib.rs 里写的是 mod inventory;,那么 main.rs 里的 use shop::inventory::count_items; 就会失败:
error[E0603]: module `inventory` is private --> src/main.rs:1:11 |1 | use shop::inventory::count_items; | ^^^^^^^^^ private module修法:在 lib.rs 里写 pub mod inventory;,并在模块里写 pub fn count_items。注意:这里 pub(crate) 不够用。对库来说,main.rs 是另一个 crate,所以 pub(crate) fn 仍然会报 function `count_items` is private。参见 Cargo 和 crate。
私有的结构体字段。 它们有自己的错误代码。创建一个带私有字段的结构体会报 E0451,读取私有字段会报 E0616:
error[E0451]: field `balance` of struct `Account` is privateerror[E0616]: field `balance` of struct `Account` is private修法:和修复 4 一样二选一。如果谁都可以改这个字段,就给它加 pub;否则保持私有,再加一个 pub fn new(...) 和一个读取方法,比如 pub fn balance(&self) -> u64。
在 crate 源码里找到的函数。 你在 GitHub 上读某个 crate 的代码,找到了一个完美的辅助函数,结果一调用就是 E0603。如果 crate 的作者没有把它设为 pub,你就用不了它,而且他们随时可能改掉它。去 docs.rs 上这个 crate 的页面找一找有没有能做同样事情的公开接口;那里只会列出 pub 的项。
- E0432:
unresolved import(无法解析的导入)。这个项根本没找到。而 E0603 是找到了,但它是私有的。 - E0433:
failed to resolve(无法解析),代码里的某条路径走不通。 - E0451 和 E0616:结构体字段是私有的(创建结构体时,或读取字段时)。
- E0624:方法或关联函数(比如
new)是私有的。 - 所有编译错误:全部错误页面的列表。
小测验
1. 代码是 mod shop { mod prices { pub fn price_of() {} } },main 调用了 shop::prices::price_of()。你会得到哪个错误?
2. src/lib.rs 里有一个标了 pub(crate) 的辅助函数。同一个包里的 src/main.rs 能调用它吗?
3. pub struct Email(String); 故意让字段保持私有。让其他模块创建它的友好方式是什么?
得分: 0 / 3
- E0603 — Rust 错误代码索引(英文):官方说明,附一个最小示例。
- 引用模块树中项的路径 — Rust 程序设计语言:用
pub公开路径,以及pub用在结构体和枚举上的效果。 - 使用 use 关键字将路径引入作用域 — Rust 程序设计语言:用
pub use重新导出名称。 - 可见性 — 通过例子学 Rust:在一个可运行的示例里展示
pub、pub(crate)、pub(super)和pub(in path)。 - Visibility and privacy — The Rust Reference(英文):想了解全貌时可以看的精确规则。