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

Lỗi Rust E0603: function, struct or module is private (mục riêng tư)

E0603 thật ra là tin vui đội lốt: Rust đã tìm thấy hàm, struct hoặc module mà bạn yêu cầu. Chỉ là nó không được phép đưa cho bạn, vì mục đó là riêng tư (private). Mọi thứ trong một module Rust đều riêng tư cho tới khi bạn nói khác, nên lỗi này xuất hiện ngay lần đầu gần như ai cũng tách code ra thành module. Cách sửa thường chỉ là một từ, pub. Trang này thuộc hướng dẫn về lỗi trình biên dịch.

error[E0603]: function `apply_discount` is private
--> src/main.rs:8:26
|
8 | let total = billing::apply_discount(200); // ❌ private
| ^^^^^^^^^^^^^^ private function
|
note: the function `apply_discount` is defined here
--> src/main.rs:2:5
|
2 | fn apply_discount(price: u32) -> u32 { // no `pub`
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Trình biên dịch báo rằng hàm apply_discount là riêng tư, và chỉ cho bạn chỗ nó được định nghĩa.

Đây là chương trình gây ra lỗi:

mod billing {
fn apply_discount(price: u32) -> u32 { // no `pub`
price * 90 / 100
}
}
fn main() {
let total = billing::apply_discount(200); // ❌ private
println!("Total: {total}");
}

Các chú thích giữ tiếng Anh vì chúng xuất hiện trong thông báo lỗi: hàm không có pub, và lời gọi ở dòng 8 chạm vào một mục riêng tư.

Dòng đầu tiên nêu loại của mục đó, nên cùng một lỗi có vài biến thể:

Dòng đầu tiên ghi gì Thường sai ở đâu Cách sửa
function `apply_discount` is private fn không có pub Cách sửa 1 hoặc Cách sửa 2
struct `Order` is private struct không có pub Cách sửa 1
module `prices` is private một mod trên đường dẫn không có pub Cách sửa 1 hoặc Cách sửa 3
function import `price_of` is private bạn với tới một mục qua một use thường bên trong module khác Cách sửa 3
tuple struct constructor `Percent` is private struct là pub, nhưng các trường của nó thì không Cách sửa 4

Hãy đọc hai mũi tên. Mũi tên thứ nhất (dòng 8) là chỗ bạn dùng mục đó. Mũi tên của note: (dòng 2) là chỗ mục đó được định nghĩa. Rust đang nói: “Tôi thấy apply_discount ở đằng kia, nhưng nó chưa bao giờ được đánh dấu là dùng chung, nên code bên ngoài billing không gọi được nó.”

File nào không quan trọng. Nếu billing nằm trong src/billing.rs, bạn vẫn gặp đúng lỗi đó, chỉ là note: sẽ trỏ tới src/billing.rs:1:1.

Hãy nghĩ tới một tòa nhà văn phòng. Mỗi module là một căn phòng có cửa. Một mục riêng tư là một dụng cụ ở yên trong phòng của nó: người trong phòng (và trong những phòng nhỏ hơn bên trong) dùng được, nhưng người ngoài hành lang thì không. pub đặt dụng cụ đó lên một cái kệ ngoài hành lang.

Rust cho mọi thứ mặc định là riêng tư: hàm, struct, trường của struct, module, và cả các dòng import use. Bạn chủ động cho phép chia sẻ bằng pub. Bài Module cho thấy điều này ngay từ đầu.

Lý do là sự an toàn cho chính bạn trong tương lai. Thứ gì là pub sẽ trở thành một lời hứa: code khác có thể phụ thuộc vào nó, nên thay đổi nó có thể làm hỏng code đó. Các mục riêng tư thì tự do thay đổi, đổi tên hay xóa, và trình biên dịch đảm bảo không ai bên ngoài dựa vào chúng. Các hàm phụ nhỏ cứ là hàm phụ, và một struct có thể bảo vệ quy tắc của riêng nó (như “số dư không bao giờ âm”) vì không ai khác chạm trực tiếp được vào các trường của nó.

Thêm một chi tiết: quyền riêng tư được kiểm tra ở từng bước của đường dẫn. shop::prices::price_of chỉ dùng được nếu bạn nhìn thấy prices và nhìn thấy price_of. Một pub fn nằm trong một module riêng tư vẫn nằm ngoài tầm với.

Cách sửa 1: Thêm pub cho mục đó (và cho mọi module trên đường dẫn)

Phần tiêu đề “Cách sửa 1: Thêm pub cho mục đó (và cho mọi module trên đường dẫn)”

Chọn cách này khi mục đó vốn được dùng từ bên ngoài module của nó. Đây là cách sửa đúng trong phần lớn trường hợp:

mod billing {
pub fn apply_discount(price: u32) -> u32 {
price * 90 / 100
}
}
fn main() {
let total = billing::apply_discount(200);
println!("Tổng: {total}");
}

Kết quả:

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

Nếu thông báo lỗi ghi module `prices` is private, thì pub bị thiếu ở module: hãy viết pub mod prices { ... }. rustc cũng báo trường hợp này bằng một nhãn thứ hai, function `price_of` is not publicly re-exported. Với file riêng, pub đặt ở dòng khai báo: pub mod prices;.

Với struct, pub struct Order làm cho kiểu trở thành công khai, nhưng từng trường vẫn cần pub riêng nếu code bên ngoài cần đọc hoặc gán nó. Đọc thêm ở Cách sửa 4.

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

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

Chọn cách này khi mục đó cần được chia sẻ, nhưng không phải cho cả thế giới. pub có vài mức, từ nhỏ đến lớn:

Bạn viết Ai dùng được mục đó
không viết gì (riêng tư) chính module của nó, và các module bên trong nó
pub(super) thêm cả module cha
pub(crate) mọi code trong crate này
pub tất cả mọi người, kể cả các crate khác phụ thuộc vào crate của bạn

Trong một chương trình (binary), pub(crate) và pub gần như giống nhau. Sự khác biệt quan trọng trong một thư viện: pub(crate) cho các module của bạn dùng chung một hàm phụ mà không biến nó thành một phần của API công khai của thư viện.

mod shop {
// Mọi code trong crate này đều gọi được hàm này.
pub(crate) fn store_name() -> &'static str {
"Ferris Fruits"
}
pub mod receipt {
// Chỉ module cha, `shop`, được gọi hàm phụ này.
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!("Chào mừng đến với {}", shop::store_name());
shop::print_receipt(&[("táo", 3), ("lê", 5)]);
// shop::receipt::line("táo", 3); // ❌ E0603: `line` chỉ dành cho `shop`
}

Kết quả:

Chào mừng đến với Ferris Fruits
== Ferris Fruits ==
táo: 3
lê: 5
▶ Tự chạy thử

Bỏ dấu // ở đầu dòng cuối và bạn sẽ gặp function `line` is private, dù nó ghi pub(super). main không phải module cha của receipt, nên cánh cửa vẫn đóng.

Chọn cách này khi module bên trong là chi tiết nội bộ, nhưng có một mục trong đó cần dễ với tới. Giữ module riêng tư và đặt mục đó lên một đường dẫn ngắn hơn, công khai:

mod shop {
mod prices {
// vẫn riêng tư: giá được lưu thế nào là việc của shop
pub fn price_of(item: &str) -> u32 {
if item == "táo" { 3 } else { 5 }
}
}
pub use prices::price_of; // giờ với tới được qua shop::price_of
}
fn main() {
println!("Táo giá {}", shop::price_of("táo"));
println!("Lê giá {}", shop::price_of("lê"));
}

Kết quả:

Táo giá 3
Lê giá 5
▶ Tự chạy thử

Đây cũng là cách sửa cho function import `price_of` is private. Một dòng use prices::price_of; thường bên trong shop là lối tắt chỉ dành cho shop; như mọi thứ khác, một use là riêng tư. pub use biến lối tắt đó thành một cánh cửa công khai. Các crate lớn làm vậy suốt, nên bạn có thể viết serde::Serialize mà không cần biết nó nằm trong module con nào.

Cách sửa 4: Thêm pub fn new thay vì công khai các trường

Phần tiêu đề “Cách sửa 4: Thêm pub fn new thay vì công khai các trường”

Chọn cách này khi lỗi liên quan tới các trường của struct hoặc constructor của nó. Ở đây struct là pub, nhưng trường của nó thì không:

mod billing {
pub struct Percent(u8);
}
fn main() {
let discount = billing::Percent(150); // ❌ private constructor
}
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 constructor
| ^^^^^^^ private tuple struct constructor
|
help: consider making the field publicly accessible
|
2 | pub struct Percent(pub u8);
| +++

Rust giải thích rằng constructor là riêng tư nếu có bất kỳ trường nào riêng tư. rustc gợi ý pub u8, và với một kiểu dữ liệu đơn giản thì như vậy cũng ổn. Nhưng hãy nhìn giá trị: 150 phần trăm! Thường thì trường riêng tư chính là mục đích. Module muốn kiểm tra giá trị trước khi một Percent được tạo ra. Hãy cho nó một constructor công khai làm việc kiểm tra đó:

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 giảm 10%: {}", ten.apply(200));
println!("150% có hợp lệ không? {}", billing::Percent::new(150).is_some());
}

Kết quả:

200 giảm 10%: 180
150% có hợp lệ không? false
▶ Tự chạy thử

Giờ thì mọi Percent trong toàn bộ chương trình đều được đảm bảo nằm trong khoảng 0 đến 100, và apply không bao giờ bị tràn số âm. Nhớ rằng chính new cũng cần pub; một phương thức riêng tư sẽ cho ra lỗi khác, E0624 (associated function `new` is private). Xem Struct để biết thêm về constructor.

Một package có cả main.rs lẫn lib.rs. Đó là hai crate riêng biệt, và main.rs dùng thư viện từ bên ngoài. Nếu lib.rs ghi mod inventory;, thì use shop::inventory::count_items; trong main.rs sẽ lỗi:

error[E0603]: module `inventory` is private
--> src/main.rs:1:11
|
1 | use shop::inventory::count_items;
| ^^^^^^^^^ private module

Rust báo rằng module inventory là riêng tư. Cách sửa: viết pub mod inventory; trong lib.rs và pub fn count_items trong module đó. Cẩn thận: ở đây pub(crate) là không đủ. Với thư viện, main.rs là một crate khác, nên một pub(crate) fn vẫn cho ra function `count_items` is private. Xem Cargo và crate.

Trường riêng tư của struct. Những trường hợp này có mã lỗi riêng. Tạo một struct có trường riêng tư sẽ gây E0451, còn đọc một trường như vậy sẽ gây E0616:

error[E0451]: field `balance` of struct `Account` is private
error[E0616]: field `balance` of struct `Account` is private

Cách sửa: lựa chọn giống Cách sửa 4. Thêm pub cho trường nếu ai cũng được phép thay đổi nó, hoặc giữ nó riêng tư và thêm pub fn new(...) cùng một getter như pub fn balance(&self) -> u64.

Một hàm bạn tìm thấy trong mã nguồn của một crate. Bạn đọc một crate trên GitHub, tìm được đúng hàm phụ mình cần, và gặp E0603 khi gọi nó. Nếu tác giả crate không để nó pub, bạn không dùng được, và họ có thể thay đổi nó bất cứ lúc nào. Hãy tìm một cách công khai để làm cùng việc đó trên trang của crate ở docs.rs; chỉ những mục pub mới xuất hiện ở đó.

  • E0432: unresolved import. Hoàn toàn không tìm thấy mục đó. Với E0603, mục đó đã được tìm thấy, nhưng là riêng tư.
  • E0433: failed to resolve, một đường dẫn trong code của bạn không dẫn tới đâu cả.
  • E0451 và E0616: một trường của struct là riêng tư (khi tạo struct, hoặc khi đọc trường).
  • E0624: một phương thức hoặc hàm liên kết (như new) là riêng tư.
  • Tất cả lỗi trình biên dịch: danh sách đầy đủ các trang lỗi.

Kiểm tra nhanh

1. mod shop { mod prices { pub fn price_of() {} } } và main gọi shop::prices::price_of(). Bạn gặp lỗi nào?

2. Một hàm phụ trong src/lib.rs được đánh dấu pub(crate). src/main.rs trong cùng package có gọi được nó không?

3. Một pub struct Email(String); cố ý giữ trường của nó riêng tư. Cách thân thiện để cho module khác tạo nó là gì?

Điểm: 0 / 3