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

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.)

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);
}

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.

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.

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"),
email: String::from("[email protected]"), // ✅ đã thêm
active: true,
};
println!("{} <{}> đang hoạt động: {}", user.name, user.email, user.active);
}

Kết quả:

Ferris <[email protected]> đang hoạt động: true
▶ Tự chạy thử

Cá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 }
▶ Tự chạy thử

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)]`.

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 1
Bo: điểm 50, cấp 1
▶ Tự chạy thử

Struct 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 }
▶ Tự chạy thử

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.

Đô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"),
email: String::from("[email protected]"),
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: None
▶ Tự chạy thử

Hã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.

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 đó.

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