跳转到内容

Rust 错误 E0624:method is private(私有方法)

E0624 的意思是:你调用的方法确实存在,但它写的时候没加 pub,所以只有它所在模块里的代码才能调用。 结构体可以是公有的,模块也可以是公有的,但方法还是得一个一个地从私有开始。刚把一个类型挪进单独的模块时,这个错误特别常见,修法通常就是在 fn 前面加上 pub。这一页属于 Rust 编译错误详解系列。

error[E0624]: associated function `new` is private
--> src/main.rs:18:38
|
7 | fn new() -> Account { // 没有 `pub`
| ------------------- private associated function defined here
...
18 | let mut account = bank::Account::new(); // ❌ 私有的关联函数
| ^^^ private associated function
error[E0624]: method `add_interest` is private
--> src/main.rs:19:13
|
11 | fn add_interest(&mut self) { // 没有 `pub`
| -------------------------- private method defined here
...
19 | account.add_interest(); // ❌ 私有方法
| ^^^^^^^^^^^^ private method

两条错误的意思分别是“关联函数 new 是私有的”和“方法 add_interest 是私有的”。defined here 标出了它们定义的位置。

产生这个错误的程序是这样的:

mod bank {
pub struct Account {
balance: u64,
}
impl Account {
fn new() -> Account { // 没有 `pub`
Account { balance: 0 }
}
fn add_interest(&mut self) { // 没有 `pub`
self.balance += self.balance / 100;
}
}
}
fn main() {
let mut account = bank::Account::new(); // ❌ 私有的关联函数
account.add_interest(); // ❌ 私有方法
}

rustc 在这里用了两个名字。方法(method)接收 self(account.add_interest())。关联函数(associated function)没有 self,要用 :: 调用(Account::new())。两者的规则和修法都一样。

每条错误都有两个箭头。^^^ 箭头是你调用的地方,--- 箭头指向缺了 pub 的那一行 fn。所以错误信息准确地告诉了你该在哪里加 pub。

写在 impl Account 里面,并不会让方法变成公有的,哪怕 Account 是 pub 的。想想餐馆:菜单是公开的,你可以点菜(pub 方法)。厨房里有自己的工序,比如“切洋葱”,你没法直接点这个。那些就是私有方法。

Rust 里的一切默认都是私有的,方法也不例外(参见模块)。私有性属于写 impl 块的那个模块:mod bank 里的任何代码都能调用 add_interest,外面的代码不行。

这让一个类型可以保留一些只有按正确顺序调用才有意义的小辅助方法。假设 add_interest 应该每月运行一次,而且要在扣完手续费之后。如果谁都能调用它,迟早有人会不小心调用两次。私有的辅助方法,加上一个按正确顺序运行它们的公有方法,就能让这种错误不可能发生。

它还给你留了余地。私有方法随时可以改名、合并或删除,因为编译器保证外面没有任何代码在用它们。

当这个方法本来就是要给外面调用的时,选这个方法。对 new 来说几乎总是这样:一个谁都调用不了的构造函数没什么用。

mod bank {
pub struct Account {
balance: u64,
}
impl Account {
pub fn new(opening: u64) -> Account {
Account { balance: opening }
}
pub fn add_interest(&mut self) {
self.balance += self.balance / 100; // 1%
}
pub fn balance(&self) -> u64 {
self.balance
}
}
}
fn main() {
let mut account = bank::Account::new(1000);
account.add_interest();
println!("余额:{}", account.balance());
}

输出:

余额:1010
▶ 动手试一试

修复 2:调用为你准备的公有方法

Section titled “修复 2:调用为你准备的公有方法”

当这个私有方法只是个辅助方法时,选这个方法,尤其是在别人写的代码里。通常会有一个公有方法替你调用它,而且顺序正确、检查到位。在下面的例子里,让 add_interest 保持私有才是正确的设计:

mod bank {
pub struct Account {
balance: u64,
}
impl Account {
pub fn new(opening: u64) -> Account {
Account { balance: opening }
}
// 公有的入口:一次调用就按正确顺序处理完整个月。
pub fn close_month(&mut self) {
self.charge_fee();
self.add_interest();
}
pub fn balance(&self) -> u64 {
self.balance
}
// 私有的辅助方法:单独调用或调用两次,结果都会出错。
fn charge_fee(&mut self) {
self.balance = self.balance.saturating_sub(5);
}
fn add_interest(&mut self) {
self.balance += self.balance / 100;
}
}
}
fn main() {
let mut account = bank::Account::new(1000);
account.close_month();
println!("一个月后:{}", account.balance());
// account.add_interest(); // ❌ E0624: method `add_interest` is private
}

输出:

一个月后:1004
▶ 动手试一试

一条好用的规则:只有当一个方法单独调用、在任何时候调用都不会破坏结构体时,才把它设为 pub。如果它只有作为某件更大的事情中的一步才有意义,就让它保持私有,把那件更大的事公开出去。

修复 3:用 pub(crate) 表示“只给我自己的代码用”

Section titled “修复 3:用 pub(crate) 表示“只给我自己的代码用””

当你的 crate 里的其他模块需要这个方法,但使用你的库的人不应该用它时,选这个方法。pub(super)(只有父模块)的用法也一样;E0603 页面有一张列出所有级别的表。

mod shop {
pub struct Cart {
prices: Vec<u32>,
}
impl Cart {
pub fn new() -> Cart {
Cart { prices: Vec::new() }
}
pub fn add(&mut self, price: u32) {
self.prices.push(price);
}
// 只给我们自己的代码用(比如打印收据),不给库的使用者。
pub(crate) fn total(&self) -> u32 {
self.prices.iter().sum()
}
}
}
mod receipt {
pub fn print(cart: &crate::shop::Cart) {
println!("总计:{}", cart.total());
}
}
fn main() {
let mut cart = shop::Cart::new();
cart.add(3);
cart.add(5);
receipt::print(&cart);
}

输出:

总计:8
▶ 动手试一试

有一个坑:在同时有 src/lib.rs 和 src/main.rs 的包里,它们是两个 crate。lib.rs 里的 pub(crate) fn total 对 main.rs 来说是私有的:

error[E0624]: method `total` is private
--> src/main.rs:3:25
|
3 | println!("{}", cart.total());
| ^^^^^ private method
|
::: src/lib.rs:10:5
|
10 | pub(crate) fn total(&self) -> u32 {
| --------------------------------- private method defined here

这种情况下,就用普通的 pub。参见 Cargo 与 crate。

修复 4:把需要它的代码挪进模块里

Section titled “修复 4:把需要它的代码挪进模块里”

当这个方法确实应该保持私有,但某些代码偏偏需要它时(比如审计检查或测试),选这个方法。同一个模块里的代码可以调用它,子模块也可以:bank 里面的模块能看到 bank 里所有私有的东西。

mod bank {
pub struct Account {
balance: u64,
}
impl Account {
pub fn new(opening: u64) -> Account {
Account { balance: opening }
}
fn is_suspicious(&self) -> bool {
self.balance > 10_000
}
}
// 子模块可以使用父模块里所有私有的东西。
pub mod audit {
pub fn check(account: &super::Account) -> &'static str {
if account.is_suspicious() { "需要复核" } else { "正常" }
}
}
}
fn main() {
let small = bank::Account::new(500);
let big = bank::Account::new(50_000);
println!("小额账户:{}", bank::audit::check(&small));
println!("大额账户:{}", bank::audit::check(&big));
}

输出:

小额账户:正常
大额账户:需要复核
▶ 动手试一试

单元测试也是同样的道理。写在 bank 里面(或 src/bank.rs 底部)的 mod tests 是一个子模块,所以它可以调用私有方法。参见测试:

#[cfg(test)]
mod tests {
use super::*;
#[test]
fn not_suspicious_when_small() {
let account = Account::new(500);
assert!(!account.is_suspicious()); // 私有的,但在这里可以用
}
}

impl 块写在别的模块里。 私有性属于写 impl 的那个模块,而不是结构体。如果 Order 在 mod model 里,你却在 mod pricing 里写了 impl Order { fn with_tax(&self) ... },那么 main 会得到 method `with_tax` is private,箭头指向 mod pricing。修复:pub fn with_tax,或者 pub(crate)。

把方法当作函数值来用。 accounts.iter().map(bank::Account::audit) 只是同一个调用的另一种写法,所以会报同样的 E0624。修法也一样。

trait 方法。 impl SomeTrait for Type 里面的方法永远不用写 pub。它们和 trait 本身一样公开,加上 pub 反而会报错:error[E0449]: visibility qualifiers are not permitted here(这里不允许写可见性修饰符)。如果某个 trait 方法好像找不到,你会得到 E0599,修法是 use 这个 trait。

crate 的私有方法。 你在 GitHub 上某个 crate 的源码里找到了一个再合适不过的方法,但它不是 pub 的。你调用不了它,而且作者在任何一个版本里都可能改掉它。去 docs.rs 上这个 crate 的页面找一个能完成同样工作的公有方法;那里只会显示 pub 的项。

  • E0603:function `x` is private(函数是私有的)。同样的道理,用在独立函数、结构体和模块上。
  • E0616:field `x` of struct `Y` is private,发生在读取或写入字段时。
  • E0451:用 Y { ... } 构建结构体时遇到私有字段。通常加一个 pub fn new 就能解决。
  • E0599:no method named `x` found(找不到方法)。这个类型没有这个方法,或者它的 trait 不在作用域里。
  • 所有编译错误:错误页面的完整列表。

小测验

1. mod clock 里有 pub struct Timer 和 impl Timer { fn start() -> Timer { ... } }。main 调用 clock::Timer::start() 时会怎样?

2. 哪些代码可以调用定义在 mod bank 里的私有方法?

3. 私有的 add_interest 必须每月只运行一次,而且要在扣完手续费之后。对于 E0624,最好的修法是什么?

得分: 0 / 3