Lỗi Rust E0616: field of struct is private (field riêng tư)
E0616 nghĩa là: field (trường) bạn yêu cầu có tồn tại, nhưng nó riêng tư (private) đối với module nơi struct được định nghĩa. Bạn được phép giữ struct, chỉ là không được thò tay vào bên trong nó. Ai cũng gặp lỗi này khi lần đầu chuyển một struct vào module riêng, và khi gõ v.len thay vì v.len(). Cách sửa thường là gọi một phương thức. Trang này thuộc loạt bài Giải thích các lỗi trình biên dịch Rust.
Thông báo lỗi
Phần tiêu đề “Thông báo lỗi”error[E0616]: field `balance` of struct `Account` is private --> src/main.rs:16:50 |16 | println!("{} has {}", account.owner, account.balance); // ❌ private field | ^^^^^^^ private fieldTrình biên dịch báo field balance của struct Account là riêng tư. Đây là chương trình gây ra lỗi:
mod bank { pub struct Account { pub owner: String, balance: u64, // private }
impl Account { pub fn new(owner: &str) -> Account { Account { owner: owner.to_string(), balance: 0 } } }}
fn main() { let account = bank::Account::new("Ferris"); println!("{} has {}", account.owner, account.balance); // ❌ private field}Cùng một lỗi này có vài biến thể:
| Bạn viết | rustc nói | Cách sửa |
|---|---|---|
account.balance |
field `balance` of struct `Account` is private |
Cách sửa 2 hoặc 3 |
scores.len trên một Vec |
field `len` of struct `Vec` is private, kèm help: a method `len` also exists, call it with parentheses |
Cách sửa 1 |
timeout.secs trên một Duration |
field `secs` of struct `Duration` is private |
Cách sửa 1 (as_secs()) |
account.balance = 500 |
cùng lỗi E0616: ghi cũng bị chặn | Cách sửa 2 |
Ý nghĩa
Phần tiêu đề “Ý nghĩa”Mũi tên chỉ vào tên field sau dấu chấm. account.owner trên cùng dòng thì không sao, vì owner được đánh dấu pub. balance thì không, nên chỉ code bên trong mod bank mới được đọc hay thay đổi nó.
Hãy để ý rằng bản thân Account là pub. Đây là điều khiến nhiều người bất ngờ: cho struct công khai không làm cho các field của nó công khai. Mỗi field cần pub riêng, nếu không thì vẫn riêng tư.
Hãy nghĩ đến một ngân hàng. Bạn có thể bước vào, và ai cũng thấy tên trên tài khoản của bạn. Nhưng bạn không thể đi vòng ra sau quầy và mở két để đếm tiền. Bạn phải hỏi giao dịch viên, và giao dịch viên làm theo quy định của ngân hàng.
Vì sao Rust làm vậy
Phần tiêu đề “Vì sao Rust làm vậy”Trong Rust, mọi thứ mặc định là riêng tư: hàm, module, và cả field của struct (xem Module). Tính riêng tư gắn với module, không phải với kiểu. Mọi code bên trong mod bank đều chạm được vào balance, còn code bên ngoài thì không.
Mục đích là để struct giữ được quy tắc của riêng nó. Giả sử số dư ngân hàng không bao giờ được âm. Nếu mọi phần của chương trình đều viết được account.balance = ..., thì phần nào cũng có thể phá vỡ quy tắc đó, và bạn sẽ phải lục tung cả codebase để tìm xem phần nào đã làm. Nếu field là riêng tư, chỉ vài phương thức bên trong mod bank mới thay đổi được nó. Kiểm tra quy tắc ở đó, là nó đúng ở mọi nơi. Ý tưởng này gọi là đóng gói (encapsulation).
Thư viện chuẩn cũng làm như vậy. Một Vec có các field riêng tư cho độ dài và vùng nhớ đệm của nó. Nếu bạn viết được v.len = 100 trên một vector chỉ chứa 3 phần tử, thì đọc v[50] sẽ đọc bộ nhớ ngẫu nhiên. Giữ len riêng tư chính là điều làm cho Vec an toàn khi dùng.
Cách sửa
Phần tiêu đề “Cách sửa”Cách sửa 1: Gọi phương thức thay vì đọc field
Phần tiêu đề “Cách sửa 1: Gọi phương thức thay vì đọc field”Chọn cách này khi struct đến từ thư viện chuẩn hoặc một crate khác. Bạn không thể sửa code của họ, và họ luôn cung cấp phương thức cho những gì bạn được phép xem. Khi rustc nói help: a method ... also exists, hãy thêm ():
use std::time::Duration;
fn main() { let scores = vec![10, 20, 30]; println!("{} điểm số", scores.len());
let timeout = Duration::from_secs(90); println!("Thời gian chờ: {} giây", timeout.as_secs());}Kết quả:
3 điểm sốThời gian chờ: 90 giâyTên phương thức không phải lúc nào cũng trùng tên field (secs được đọc bằng as_secs()). Hãy xem trang của kiểu đó trên docs.rs, hoặc để editor liệt kê các phương thức sau khi bạn gõ dấu chấm.
Cách sửa 2: Thêm getter, và các phương thức tuân theo quy tắc
Phần tiêu đề “Cách sửa 2: Thêm getter, và các phương thức tuân theo quy tắc”Chọn cách này khi đó là struct của chính bạn và field có một quy tắc cần bảo vệ. Giữ field riêng tư, thêm một pub fn trả về giá trị của nó (một getter), và chỉ thay đổi nó qua các phương thức có kiểm tra quy tắc:
mod bank { pub struct Account { pub owner: String, balance: i64, // riêng tư: chỉ module này thay đổi được }
impl Account { pub fn new(owner: &str) -> Account { Account { owner: owner.to_string(), balance: 0 } }
// Getter: ai cũng được đọc số dư. pub fn balance(&self) -> i64 { self.balance }
pub fn deposit(&mut self, amount: i64) { if amount > 0 { self.balance += amount; } }
// Quy tắc nằm ở đây: số dư không bao giờ xuống dưới 0. pub fn withdraw(&mut self, amount: i64) -> Result<(), String> { if amount > self.balance { return Err(format!("chỉ còn {} trong tài khoản", self.balance)); } self.balance -= amount; Ok(()) } }}
fn main() { let mut account = bank::Account::new("Ferris"); account.deposit(100); println!("{} có {}", account.owner, account.balance());
match account.withdraw(500) { Ok(()) => println!("Đã rút 500"), Err(e) => println!("Không rút được 500: {e}"), } println!("Số dư vẫn là {}", account.balance());}Kết quả:
Ferris có 100Không rút được 500: chỉ còn 100 trong tài khoảnSố dư vẫn là 100Rust cho phép phương thức và field trùng tên, nên balance() là tên thông dụng cho getter (chứ không phải get_balance()). Khi getter đã có, rustc thậm chí còn gợi ý nó: help: a method `balance` also exists, call it with parentheses. deposit và withdraw là các setter của bạn: chúng thay đổi field, nhưng chỉ theo những cách mà quy tắc cho phép. Xem thêm về phương thức ở Struct.
Cách sửa 3: Cho field là pub khi dữ liệu đơn giản
Phần tiêu đề “Cách sửa 3: Cho field là pub khi dữ liệu đơn giản”Chọn cách này khi giá trị nào cũng được và không có gì khác phụ thuộc vào field. Một điểm trên lưới, một màu sắc, hay một struct thiết lập không có quy tắc nào cần bảo vệ. Getter ở đó chỉ làm bạn gõ thêm:
mod geometry { pub struct Point { pub x: i32, pub y: i32, }}
fn main() { let mut p = geometry::Point { x: 3, y: 4 }; p.x += 1; println!("Điểm tại ({}, {})", p.x, p.y);}Kết quả:
Điểm tại (4, 4)Khi chỉ code của chính bạn mới nên thấy field, hãy viết pub(crate) x: i32 (mọi module trong crate này) hoặc pub(super) x: i32 (chỉ module cha). Trong một chương trình, chúng hoạt động như pub; trong một thư viện, chúng giữ field nằm ngoài API công khai của bạn. Trang E0603 có bảng liệt kê tất cả các mức.
Có nên cứ cho nó là pub? Hãy tự hỏi: “Có giá trị nào của field này, hoặc một thay đổi chỉ riêng trên nó, làm struct bị sai không?” Một số dư âm, một len không khớp với dữ liệu, một total không còn bằng tổng các món: hãy giữ những thứ đó riêng tư và dùng Cách sửa 2. Nếu câu trả lời là “không, giá trị nào cũng được”, thì pub là lựa chọn thẳng thắn.
Thường gặp ở đâu
Phần tiêu đề “Thường gặp ở đâu”len trên một Vec. Bạn viết scores.len và nhận field `len` of struct `Vec` is private. Đó là vì Vec thật sự có một field riêng tư tên len. Cách sửa là scores.len(). Chi tiết thú vị: name.len trên một String cho ra một lỗi khác, E0615 (attempted to take value of method), vì String không có field nào tên như vậy. Cách sửa vẫn thế.
Gán field từ bên ngoài. account.balance = 500; cũng cho ra E0616. Ghi cũng bị chặn. Đừng cho field là pub chỉ để gán nó; hãy thêm một phương thức như deposit thực hiện thay đổi và kiểm tra nó.
Sau khi chuyển struct vào module hay file riêng. Code trong main.rs đã dùng user.email suốt mấy tháng. Rồi bạn chuyển User vào src/user.rs và mọi chỗ truy cập field đều hỏng. Những field trước đây vẫn ổn giờ đã thành riêng tư đối với mod user. Hãy thêm pub cho các field là dữ liệu đơn giản, và getter cho phần còn lại.
Test. Một khối #[cfg(test)] mod tests trong cùng file là một module con, nên nó đọc được field riêng tư. Các test trong thư mục tests/ nằm ngoài crate của bạn và chỉ dùng được API pub, như mọi người dùng khác.
Lỗi liên quan
Phần tiêu đề “Lỗi liên quan”- E0603:
function `x` is private. Cùng ý tưởng, nhưng cho hàm, struct và module. - E0451: cùng một field riêng tư, nhưng khi tạo struct bằng
Account { ... }. - E0624:
method `x` is private. Phương thức (hoặcnew) cũng cầnpub. - E0609:
no field `x` on type `T`. Field hoàn toàn không tồn tại (thường do gõ nhầm). - E0615:
attempted to take value of method. Bạn viếtx.lennhưng ý làx.len(). - 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
Phần tiêu đề “Kiểm tra nhanh”Kiểm tra nhanh
1. pub struct Account { balance: u64 } nằm trong mod bank. main có đọc được account.balance không?
2. Bạn nhận `field len of struct Vec is private`. Cách sửa là gì?
3. Một struct Score có field points phải luôn nằm trong khoảng 0 đến 100. Thiết kế nào tốt nhất?
Điểm: 0 / 3
Đọc thêm
Phần tiêu đề “Đọc thêm”- E0616 — Rust error codes index (tiếng Anh): giải thích chính thức kèm một ví dụ tối giản.
- Making Structs and Enums Public — The Rust Book (tiếng Anh): vì sao một
pub structvẫn có field riêng tư, với ví dụ nhà hàng. - Encapsulation that Hides Implementation Details — The Rust Book (tiếng Anh): một struct giữ giá trị trung bình luôn đúng nhờ giữ các field riêng tư.
- Struct visibility — Rust by Example (tiếng Anh): field công khai và riêng tư cạnh nhau, trong một ví dụ chạy được.
- Structs have private fields (C-STRUCT-PRIVATE) — Rust API Guidelines (tiếng Anh): vì sao các thư viện giữ field riêng tư để có thể thay đổi chúng sau này.