Lỗi Rust E0063: missing field in initializer (thiếu field khi tạo struct)
E0063 nghĩa là: bạn đang tạo một struct bằng Name { ... }, nhưng chưa đưa giá trị cho mọi field (trường). Rust không bao giờ tự điền field giúp bạn, nên field nào cũng cần một giá trị, dù chỉ là 0, false hay None. Thông báo liệt kê tên các field còn thiếu, nên bạn biết chính xác cần thêm gì. Đây là một trong những lỗi thân thiện nhất của Rust. (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[E0063]: missing field `email` in initializer of `User` --> src/main.rs:8:16 |8 | let user = User { | ^^^^ missing `email`Trình biên dịch báo thiếu field email khi khởi tạo User. Đây là chương trình gây ra lỗi:
struct User { name: String, email: String, active: bool,}
fn main() { let user = User { name: String::from("Ferris"), active: true, // ❌ no `email` }; println!("{}", user.name);}Ý nghĩa
Phần tiêu đề “Ý nghĩa”Dòng tiêu đề nêu tên field còn thiếu (email) và struct (User). Dấu ^^^^ chỉ vào tên struct, nơi bạn bắt đầu tạo nó, chứ không chỉ vào một dòng cụ thể, vì vấn đề là thứ không có mặt ở đó.
Khi thiếu nhiều field hơn, rustc liệt kê chúng theo thứ tự bảng chữ cái. Với hai hoặc ba field, bạn thấy tất cả:
error[E0063]: missing fields `debug`, `retries` and `timeout_secs` in initializer of `Config`Với bốn field trở lên, nó hiện ba cái và đếm phần còn lại:
error[E0063]: missing fields `debug`, `port`, `retries` and 1 other field in initializer of `Config` --> src/main.rs:10:18 |10 | let config = Config { | ^^^^^^ missing `debug`, `port`, `retries` and 1 other field“1 other field” nghĩa là còn một field nữa. Hãy xem định nghĩa struct Config để thấy danh sách đầy đủ.
Giống như một tờ đơn mà ô nào cũng bắt buộc. Bạn không thể nộp nó khi ô “email” còn trống, kể cả khi bạn định điền sau.
Vì sao Rust làm vậy
Phần tiêu đề “Vì sao Rust làm vậy”Trong nhiều ngôn ngữ, một field bị thiếu sẽ âm thầm thành null, 0 hoặc chuỗi rỗng. Rồi chương trình chạy với một người dùng không có email, và nó hỏng ở một chỗ rất xa, thường là trên môi trường thật (production). Rust không có null, và nó không lén bịa ra giá trị sau lưng bạn. Một User luôn có một email thật. Xem Định nghĩa và tạo struct.
Điều đó không có nghĩa là lần nào bạn cũng phải gõ mọi field. Rust cho bạn những cách ngắn gọn để nói “dùng giá trị mặc định” hoặc “sao chép phần còn lại từ giá trị kia”, như các cách sửa dưới đây. Điều quan trọng là bạn chọn, và lựa chọn đó được viết rõ trong code.
Cách sửa
Phần tiêu đề “Cách sửa”Cách sửa 1: Thêm field còn thiếu
Phần tiêu đề “Cách sửa 1: Thêm field còn thiếu”Hầu hết thời gian hãy chọn cách này. Field có mặt là có lý do, nên hãy cho nó một giá trị thật. Nếu bạn có một biến trùng tên với field, có thể dùng cách viết tắt User { name, email, active }.
struct User { name: String, email: String, active: bool,}
fn main() { let user = User { name: String::from("Ferris"), active: true, }; println!("{} <{}> đang hoạt động: {}", user.name, user.email, user.active);}Kết quả:
Ferris <[email protected]> đang hoạt động: trueCách sửa 2: Derive Default và dùng ..Default::default()
Phần tiêu đề “Cách sửa 2: Derive Default và dùng ..Default::default()”Chọn cách này khi struct có nhiều field và phần lớn có một giá trị “rỗng” hiển nhiên, như các thiết lập hay tùy chọn. Thêm #[derive(Default)] cho struct. Sau đó chỉ viết những field bạn quan tâm và kết thúc bằng ..Default::default(). Dấu .. nghĩa là “lấy mọi field tôi chưa viết từ giá trị này”.
#[derive(Debug, Default)]struct Config { host: String, port: u16, debug: bool, timeout_secs: u64, retries: u8,}
fn main() { let config = Config { host: String::from("localhost"), port: 8080, ..Default::default() // ✅ debug, timeout_secs và retries lấy giá trị mặc định }; println!("{:?}", config);}Kết quả:
Config { host: "localhost", port: 8080, debug: false, timeout_secs: 0, retries: 0 }Giá trị mặc định khi derive là 0 cho số, false cho bool, String rỗng và None cho Option. Nếu 0 là giá trị mặc định tệ (thời gian chờ 0 giây có lẽ là vậy), hãy tự viết impl Default for Config với giá trị tốt hơn. Xem Default: một giá trị khởi đầu hợp lý.
Nếu bạn quên #[derive(Default)], lỗi sẽ đổi thành E0277: the trait bound `Config: Default` is not satisfied, kèm help: consider annotating `Config` with `#[derive(Default)]`.
Cách sửa 3: Viết một hàm khởi tạo new
Phần tiêu đề “Cách sửa 3: Viết một hàm khởi tạo new”Chọn cách này khi bạn tạo struct ở nhiều chỗ, hoặc khi một số field luôn bắt đầu giống nhau. Đặt struct literal đầy đủ trong một hàm, và gọi hàm đó ở mọi chỗ khác. Khi sau này bạn thêm field, bạn chỉ cập nhật new, không phải từng chỗ gọi.
struct Player { name: String, score: u32, level: u8,}
impl Player { fn new(name: &str) -> Player { Player { name: name.to_string(), score: 0, // mọi người chơi mới bắt đầu với 0 điểm... level: 1, // ...ở cấp 1 } }}
fn main() { let ana = Player::new("Ana"); let mut bo = Player::new("Bo"); bo.score += 50; println!("{}: điểm {}, cấp {}", ana.name, ana.score, ana.level); println!("{}: điểm {}, cấp {}", bo.name, bo.score, bo.level);}Kết quả:
Ana: điểm 0, cấp 1Bo: điểm 50, cấp 1Struct literal bên trong new vẫn cần mọi field, nên một field bị quên sẽ hiện ra ở đó dưới dạng E0063. Không sao: bạn chỉ sửa một lần. Xem Hàm kiểu constructor.
Cách sửa 4: Sao chép phần còn lại từ giá trị khác bằng ..other
Phần tiêu đề “Cách sửa 4: Sao chép phần còn lại từ giá trị khác bằng ..other”Chọn cách này khi bạn đã có một giá trị tương tự và muốn một bản sao với vài thay đổi. ..base lấy mọi field bạn chưa viết từ base:
#[derive(Debug, Clone)]struct Theme { name: String, font_size: u8, dark: bool,}
fn main() { let base = Theme { name: String::from("Mặc định"), font_size: 14, dark: false };
// chỉ `name` và `dark` thay đổi, phần còn lại lấy từ `base` let night = Theme { name: String::from("Ban đêm"), dark: true, ..base };
// `..base` sẽ move `name` ra khỏi base, nên clone nó để còn dùng base tiếp let large = Theme { font_size: 20, ..base.clone() };
println!("{:?}", night); println!("{:?}", large); println!("{:?}", base);}Kết quả:
Theme { name: "Ban đêm", font_size: 14, dark: true }Theme { name: "Mặc định", font_size: 20, dark: false }Theme { name: "Mặc định", font_size: 14, dark: false }Một điều cần biết: ..base move (di chuyển) các field như String ra khỏi base. Ở night thì không sao, vì chúng ta đã tự viết name, nên chỉ các field Copy (font_size) được lấy từ base. Ở large, name sẽ được lấy từ base, nên chúng ta dùng ..base.clone() để base vẫn dùng được. Nếu bạn bỏ clone và dùng base sau đó, bạn sẽ nhận E0382.
Khi field Option có ích
Phần tiêu đề “Khi field Option có ích”Đôi khi một field thật sự có thể trống: số điện thoại người dùng chưa cung cấp, một biệt danh, một ngày kết thúc. Hãy cho field đó là Option. Bạn vẫn phải viết nó ra (Rust không tự điền None giúp bạn), nhưng giờ None là một giá trị trung thực, được phép. Xem Option.
#[derive(Debug, Default)]struct Contact { name: String, email: String, phone: Option<String>, // có thể thiếu nickname: Option<String>,}
fn main() { let a = Contact { name: String::from("Ana"), phone: None, // viết rõ ra: "chưa có số điện thoại" nickname: Some(String::from("Annie")), }; // với Default, mọi Option bạn bỏ qua sẽ thành None let b = Contact { name: String::from("Bo"), ..Default::default() };
println!("{} điện thoại: {:?}, biệt danh: {:?}", a.name, a.phone, a.nickname); println!("{} email: {:?}, điện thoại: {:?}", b.name, b.email, b.phone);}Kết quả:
Ana điện thoại: None, biệt danh: Some("Annie")Bo email: "", điện thoại: NoneHãy để ý b.email là "". Đó là cái bẫy của Default: một field bắt buộc âm thầm trở thành rỗng. Chỉ dùng Option cho những thứ thật sự không bắt buộc, và giữ các field bắt buộc như email ở kiểu thường mà bạn luôn phải điền.
Thường gặp ở đâu
Phần tiêu đề “Thường gặp ở đâu”Ngay sau khi bạn thêm một field vào struct. Bạn thêm created_at vào User, và đột nhiên mọi User { ... } trong project đều cho ra E0063. Đó là Rust đang giúp bạn: mỗi chỗ là một chỗ bạn cần suy nghĩ. Một hàm khởi tạo new (Cách sửa 3) giữ cho danh sách này ngắn.
Biến thể enum có field được đặt tên. Shape::Rectangle { width: 2.0 } cho ra missing field `height` in initializer of `Shape`. Hãy để ý thông báo nêu tên enum, không phải tên biến thể. Cách sửa: cho mọi field của biến thể đó một giá trị. Xem Enum có thể chứa dữ liệu.
Một hàm trả về struct. Trong fn new_player(name: &str) -> Player { Player { name: name.to_string(), score: 0 } }, mũi tên chỉ vào Player bên trong hàm, kèm missing `level`. Cách sửa: thêm level vào literal đó.
Lỗi liên quan
Phần tiêu đề “Lỗi liên quan”- E0560: struct has no field named x: ngược lại, một field bạn viết mà struct không có. Một lỗi gõ nhầm thường cho ra cả hai cùng lúc.
- E0559: variant has no field named x: cùng sai lầm về tên với biến thể enum.
- E0277: trait bound not satisfied: bạn viết
..Default::default()nhưng struct không deriveDefault. - E0451: field is private: struct đến từ module khác và một số field là riêng tư, nên bạn không điền được. Nếu bạn bỏ qua một field riêng tư, bạn không nhận E0063 mà nhận “cannot construct
Accountwith struct literal syntax due to private fields”. Dù thế nào, hãy dùng hàmnewcủa module. - E0382: use of moved value: bạn dùng một giá trị sau khi
..valueđã move các field của nó đi. - Giải thích tất cả các lỗi trình biên dịch
Kiểm tra nhanh
Phần tiêu đề “Kiểm tra nhanh”Kiểm tra nhanh
1. rustc nói: missing fields a, b, c and 2 other fields. Có bao nhiêu field bị thiếu?
2. Struct của bạn có một field Option<String> mà bạn chưa cần. Bạn viết gì cho nó?
3. Config { port: 80, ..Default::default() } cần gì để biên dịch được?
Điểm: 0 / 3
Đọc thêm
Phần tiêu đề “Đọc thêm”- E0063 — Rust error codes index (tiếng Anh): giải thích chính thức về lỗi này.
- Defining and Instantiating Structs — The Rust Book (tiếng Anh): struct literal, cách viết tắt khi khởi tạo field và cú pháp cập nhật struct.
Default— thư viện chuẩn Rust (tiếng Anh):#[derive(Default)], tự viếtimpl Default, và..Default::default().- Struct expressions — The Rust Reference (tiếng Anh): quy tắc chính xác cho
Name { field: value }và..base. - Constructors — Rust API Guidelines (tiếng Anh): vì sao code Rust dùng hàm
newđể tạo struct.