Bỏ qua để đến nội dung

Lỗi Rust E0624: method is private (phương thức riêng tư)

E0624 nghĩa là: phương thức (method) bạn gọi có tồn tại, nhưng nó được viết không có pub, nên chỉ code bên trong module của nó mới được gọi. Struct có thể công khai, module cũng vậy. Nhưng các phương thức vẫn bắt đầu ở trạng thái riêng tư, từng cái một. Lỗi này rất hay gặp ngay sau khi bạn chuyển một kiểu vào module riêng, và cách sửa thường là thêm pub trước fn. Trang này thuộc loạt bài Giải thích các lỗi trình biên dịch Rust.

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

Trình biên dịch báo rằng hàm liên kết new và phương thức add_interest đều là riêng tư, và chỉ ra nơi chúng được định nghĩa. Đây là chương trình gây ra lỗi:

mod bank {
pub struct Account {
balance: u64,
}
impl Account {
fn new() -> Account { // no `pub`
Account { balance: 0 }
}
fn add_interest(&mut self) { // no `pub`
self.balance += self.balance / 100;
}
}
}
fn main() {
let mut account = bank::Account::new(); // ❌ private associated function
account.add_interest(); // ❌ private method
}

rustc dùng hai cái tên ở đây. Một method (phương thức) nhận self (account.add_interest()). Một associated function (hàm liên kết) không có self và được gọi bằng :: (Account::new()). Quy tắc và cách sửa giống nhau cho cả hai.

Mỗi lỗi có hai mũi tên. Mũi tên ^^^ là lời gọi của bạn. Mũi tên --- chỉ dòng fn đang thiếu pub. Vậy là thông báo nói chính xác chỗ bạn cần thêm nó.

Nằm trong impl Account không làm một phương thức trở thành công khai, kể cả khi Account là pub. Hãy nghĩ đến một nhà hàng: thực đơn là công khai, nên bạn gọi được món (các phương thức pub). Nhà bếp có những bước riêng, như “thái hành”, mà bạn không thể yêu cầu trực tiếp. Đó là các phương thức riêng tư.

Mọi thứ trong Rust mặc định là riêng tư, và phương thức cũng không ngoại lệ (xem Module). Tính riêng tư thuộc về module nơi khối impl được viết: mọi code trong mod bank đều gọi được add_interest, còn code bên ngoài thì không.

Điều này cho phép một kiểu giữ những phương thức phụ trợ nhỏ chỉ có ý nghĩa khi chạy đúng thứ tự. Giả sử add_interest chỉ nên chạy mỗi tháng một lần, sau khi đã trừ phí. Nếu ai cũng gọi được nó, sẽ có người vô tình gọi hai lần. Các hàm phụ trợ riêng tư cộng với một phương thức công khai chạy chúng đúng cách sẽ khiến sai lầm đó không thể xảy ra.

Nó cũng giữ cho bạn nhiều lựa chọn. Các phương thức riêng tư có thể đổi tên, gộp hay xóa bất cứ lúc nào, vì trình biên dịch đảm bảo không có code bên ngoài nào dùng chúng.

Chọn cách này khi phương thức được thiết kế để gọi từ bên ngoài. Với new thì gần như luôn như vậy: một hàm khởi tạo mà không ai gọi được thì chẳng có ích gì.

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!("Số dư: {}", account.balance());
}

Kết quả:

Số dư: 1010
▶ Tự chạy thử

Cách sửa 2: Gọi phương thức công khai được dành cho bạn

Phần tiêu đề “Cách sửa 2: Gọi phương thức công khai được dành cho bạn”

Chọn cách này khi phương thức riêng tư là một hàm phụ trợ, nhất là trong code không do bạn viết. Thường sẽ có một phương thức công khai gọi nó giúp bạn, đúng thứ tự và có đủ các bước kiểm tra. Ở đây, giữ add_interest riêng tư mới là thiết kế đúng:

mod bank {
pub struct Account {
balance: u64,
}
impl Account {
pub fn new(opening: u64) -> Account {
Account { balance: opening }
}
// Lối vào công khai: một lời gọi xử lý cả tháng, đúng thứ tự.
pub fn close_month(&mut self) {
self.charge_fee();
self.add_interest();
}
pub fn balance(&self) -> u64 {
self.balance
}
// Hàm phụ trợ riêng tư: gọi riêng lẻ, hoặc gọi hai lần, sẽ cho kết quả sai.
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!("Sau một tháng: {}", account.balance());
// account.add_interest(); // ❌ E0624: method `add_interest` is private
}

Kết quả:

Sau một tháng: 1004
▶ Tự chạy thử

Một quy tắc hay: chỉ cho phương thức là pub nếu việc gọi nó riêng lẻ, vào bất cứ lúc nào không thể làm hỏng struct. Nếu nó chỉ có ý nghĩa như một bước của việc gì đó lớn hơn, hãy giữ nó riêng tư và công khai việc lớn hơn đó.

Cách sửa 3: Dùng pub(crate) cho “chỉ code của tôi”

Phần tiêu đề “Cách sửa 3: Dùng pub(crate) cho “chỉ code của tôi””

Chọn cách này khi các module khác trong crate của bạn cần phương thức, nhưng người dùng thư viện của bạn thì không nên dùng. pub(super) (chỉ module cha) hoạt động theo cùng cách; trang E0603 có bảng liệt kê tất cả các mức.

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);
}
// Chỉ dành cho code của chúng ta (như hóa đơn), không cho người dùng thư viện.
pub(crate) fn total(&self) -> u32 {
self.prices.iter().sum()
}
}
}
mod receipt {
pub fn print(cart: &crate::shop::Cart) {
println!("Tổng: {}", cart.total());
}
}
fn main() {
let mut cart = shop::Cart::new();
cart.add(3);
cart.add(5);
receipt::print(&cart);
}

Kết quả:

Tổng: 8
▶ Tự chạy thử

Có một điểm cần lưu ý: trong một package có cả src/lib.rs lẫn src/main.rs, chúng là hai crate. Một pub(crate) fn total trong lib.rs là riêng tư đối với 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

Ở đó, hãy dùng pub bình thường. Xem Cargo và crate.

Cách sửa 4: Chuyển code cần nó vào trong module

Phần tiêu đề “Cách sửa 4: Chuyển code cần nó vào trong module”

Chọn cách này khi phương thức thật sự nên giữ riêng tư, nhưng có code vẫn cần đến nó, như một bước kiểm toán hay một test. Code trong cùng module gọi được nó, và một module con cũng vậy: các module bên trong bank thấy được mọi thứ riêng tư trong 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
}
}
// Một module con dùng được mọi thứ riêng tư trong module cha.
pub mod audit {
pub fn check(account: &super::Account) -> &'static str {
if account.is_suspicious() { "cần xem xét lại" } else { "ổn" }
}
}
}
fn main() {
let small = bank::Account::new(500);
let big = bank::Account::new(50_000);
println!("nhỏ: {}", bank::audit::check(&small));
println!("lớn: {}", bank::audit::check(&big));
}

Kết quả:

nhỏ: ổn
lớn: cần xem xét lại
▶ Tự chạy thử

Unit test hoạt động theo cùng cách. Một mod tests viết bên trong bank (hoặc ở cuối src/bank.rs) là một module con, nên nó được gọi các phương thức riêng tư. Xem Kiểm thử:

#[cfg(test)]
mod tests {
use super::*;
#[test]
fn not_suspicious_when_small() {
let account = Account::new(500);
assert!(!account.is_suspicious()); // riêng tư, nhưng được phép ở đây
}
}

Một khối impl ở module khác. Tính riêng tư thuộc về module nơi impl được viết, không thuộc về struct. Nếu Order nằm trong mod model nhưng bạn viết impl Order { fn with_tax(&self) ... } bên trong mod pricing, thì main sẽ nhận method `with_tax` is private, chỉ vào mod pricing. Cách sửa: pub fn with_tax, hoặc pub(crate).

Dùng phương thức như một giá trị hàm. accounts.iter().map(bank::Account::audit) là cùng một lời gọi viết theo cách khác, nên cho ra cùng lỗi E0624. Cách sửa cũng vậy.

Phương thức của trait. Bạn không bao giờ viết pub cho các phương thức bên trong impl SomeTrait for Type. Chúng công khai đúng bằng trait, và thêm pub là một lỗi: error[E0449]: visibility qualifiers are not permitted here. Nếu một phương thức của trait có vẻ bị thiếu, bạn sẽ nhận E0599, và cách sửa là use trait đó.

Phương thức riêng tư của một crate. Bạn tìm thấy đúng phương thức mình cần trong mã nguồn của một crate trên GitHub, nhưng nó không pub. Bạn không gọi được nó, và tác giả có thể thay đổi nó ở bất kỳ phiên bản nào. Hãy tìm một phương thức công khai làm được việc đó trên trang của crate ở docs.rs; chỉ các mục pub mới hiện ở đó.

  • E0603: function `x` is private. Cùng ý tưởng, cho hàm tự do, struct và module.
  • E0616: field `x` of struct `Y` is private, khi đọc hoặc ghi một field.
  • E0451: một field riêng tư khi tạo struct bằng Y { ... }. Thường sửa bằng một pub fn new.
  • E0599: no method named `x` found. Phương thức không tồn tại cho kiểu đó, hoặc trait của nó chưa nằm trong phạm vi (scope).
  • Tất cả các lỗi trình biên dịch: danh sách đầy đủ các trang lỗi.

Kiểm tra nhanh

1. pub struct Timer có impl Timer { fn start() -> Timer { ... } } bên trong mod clock. Chuyện gì xảy ra khi main gọi clock::Timer::start()?

2. Code nào được gọi một phương thức riêng tư định nghĩa trong mod bank?

3. Một add_interest riêng tư chỉ được chạy mỗi tháng một lần, sau khi trừ phí. Cách sửa E0624 tốt nhất là gì?

Điểm: 0 / 3