跳转到内容

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

看那两个箭头。第一个(第 8 行)是你使用这个项的地方,note: 的箭头(第 2 行)是这个项定义的地方。Rust 在说:“我看到那边有个 apply_discount,但它从来没有被标记为公开,所以 billing 外面的代码不能调用它。”

和文件无关。如果 billing 放在 src/billing.rs 里,你会得到同样的错误,只是 note: 改为指向 src/billing.rs:1:1。

想象一栋办公楼。每个模块都是一间有门的房间。私有项就是留在房间里的工具:房间里的人(以及房间里套着的小房间里的人)都能用,但走廊里的人用不了。pub 就是把工具放到走廊的架子上。

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: 3
pear: 5
▶ 动手试一试

把最后一行前面的 // 去掉,你就会得到 function `line` is private,尽管它写着 pub(super)。main 不是 receipt 的父模块,所以门还是关着的。

当内部模块只是实现细节,但里面某一个项应该方便访问时,选这个方法。让模块保持私有,把这个项放到一条更短的公开路径上:

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 private
error[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