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

Lỗi Rust E0026: struct or variant does not have a field named X (sai tên trường)

E0026 nghĩa là: bạn đang tách một struct hoặc một biến thể enum ra trong một mẫu (pattern), như Shape::Circle { r }, và một trong các tên trường bạn viết không tồn tại. Đây là phiên bản “mẫu” của E0560 và E0559, hai lỗi bắt cùng sai sót đó khi bạn tạo một giá trị. rustc thường cho bạn biết tên đúng trong một dòng help:, nên cách sửa thường chỉ là đổi một từ. (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[E0026]: variant `Shape::Circle` does not have a field named `r`
--> src/main.rs:8:25
|
8 | Shape::Circle { r } => 3.14 * r * r, // ❌ the field is called `radius`
| ^
| |
| variant `Shape::Circle` does not have this field
| help: `Shape::Circle` has a field named `radius`

Thông báo của trình biên dịch luôn bằng tiếng Anh: “variant Shape::Circle does not have a field named r” nghĩa là “biến thể Shape::Circle không có trường nào tên r”. Đây là chương trình gây ra lỗi đó (chú thích được giữ bằng tiếng Anh vì nó xuất hiện nguyên văn trong thông báo lỗi):

enum Shape {
Circle { radius: f64 },
Rectangle { width: f64, height: f64 },
}
fn area(shape: &Shape) -> f64 {
match shape {
Shape::Circle { r } => 3.14 * r * r, // ❌ the field is called `radius`
Shape::Rectangle { width, height } => width * height,
}
}
fn main() {
println!("{}", area(&Shape::Circle { radius: 1.0 }));
}

Bạn cũng sẽ thấy một lỗi thứ hai cho cùng dòng này: E0027, “pattern does not mention field radius” (mẫu không nhắc tới trường radius). Đó là cùng một sai sót nhìn từ phía bên kia. Rust tìm thấy một trường bạn ghi tên nhưng không tồn tại (r) và một trường có thật mà bạn không ghi tên (radius). Sửa tên là cả hai lỗi cùng biến mất.

Dòng tiêu đề nêu tên struct hoặc biến thể bạn đang so khớp (Shape::Circle) và trường bạn đã viết (r). Dấu ^ chỉ vào đúng cái tên đó bên trong cặp ngoặc nhọn của mẫu.

Trong một mẫu, các tên bên trong { } là tên trường, không phải tên biến mới do bạn tự chọn. Shape::Circle { r } là cách viết tắt của Shape::Circle { r: r }: “lấy trường tên r và đặt nó vào một biến tên r”. Circle không có trường nào tên r, nên Rust dừng lại.

Bên dưới mũi tên, rustc đưa ra một trong các gợi ý sau:

  • help: `Shape::Circle` has a field named `radius`: chỉ có đúng một trường bạn chưa nhắc tới, nên rustc đoán đó là trường bạn muốn.
  • help: a field with a similar name exists (có một trường với tên gần giống): một lỗi gõ, như nmae thay cho name. rustc cho thấy dòng đã sửa.
  • Không có gợi ý nào: rustc không đoán được. Hãy xem định nghĩa struct hoặc enum để biết tên thật.

Giống như bạn hỏi thủ thư “cuốn sách trên cái kệ tên là Gần-như-Tiểu-thuyết”. Họ biết bạn muốn một cái kệ nào đó, nhưng họ chỉ có thể đi tới những kệ có thật.

Một mẫu như Shape::Circle { radius } là một lời hứa: “khi giá trị là một Circle, hãy đưa tôi radius của nó”. Rust kiểm tra lời hứa đó với định nghĩa ngay lúc biên dịch, nên một mẫu không bao giờ đòi được dữ liệu không có thật. Xem Phân rã tuple và struct.

Ở những ngôn ngữ không có bước kiểm tra này, đọc một khóa không tồn tại sẽ cho bạn undefined hoặc null, và bug chỉ lộ ra rất lâu sau, ở chỗ cách xa lỗi gõ. Trong Rust, lỗi gõ không thể lọt qua trình biên dịch. Nó cũng bảo vệ bạn khi ai đó đổi tên một trường: mọi mẫu còn dùng tên cũ đều sáng lên với E0026, nên không chỗ nào bị bỏ quên một cách âm thầm.

Phần lớn trường hợp, hãy chọn cách này. Dùng tên trong dòng help:, hoặc xem lại định nghĩa. Tên trường phân biệt chữ hoa chữ thường và được viết theo kiểu snake_case.

Một lỗi gõ sẽ nhận gợi ý “tên gần giống” cùng dòng đã sửa:

error[E0026]: struct `User` does not have a field named `nmae`
--> src/main.rs:8:16
|
8 | let User { nmae, age } = user; // ❌ typo: the field is `name`
| ^^^^ struct `User` does not have this field
|
help: a field with a similar name exists
|
8 - let User { nmae, age } = user; // ❌ typo: the field is `name`
8 + let User { name, age } = user; // ❌ typo: the field is `name`
|

rustc đề xuất đổi nmae thành name. Chương trình đằng sau lỗi này:

struct User {
name: String,
age: u32,
}
fn main() {
let user = User { name: String::from("Ferris"), age: 7 };
let User { nmae, age } = user; // ❌ typo: the field is `name`
println!("{} is {} years old", nmae, age);
}

Hãy viết tên trường y hệt như trong định nghĩa. Khi đó cách viết tắt hoạt động, và bạn có các biến cùng tên với trường:

struct User {
name: String,
age: u32,
}
enum Shape {
Circle { radius: f64 },
Rectangle { width: f64, height: f64 },
}
fn area(shape: &Shape) -> f64 {
match shape {
Shape::Circle { radius } => 3.14 * radius * radius, // ✅ đúng tên trường
Shape::Rectangle { width, height } => width * height,
}
}
fn main() {
let shapes = [
Shape::Circle { radius: 2.0 },
Shape::Rectangle { width: 3.0, height: 4.0 },
];
for shape in &shapes {
println!("diện tích: {:.2}", area(shape));
}
let user = User { name: String::from("Ferris"), age: 7 };
let User { name, age } = user; // ✅ `name`, không phải `nmae`
println!("{} năm nay {} tuổi", name, age);
}

Kết quả:

diện tích: 12.56
diện tích: 12.00
Ferris năm nay 7 tuổi
▶ Tự chạy thử

Cách sửa 2: Giữ tên biến của riêng bạn với field: name

Phần tiêu đề “Cách sửa 2: Giữ tên biến của riêng bạn với field: name”

Chọn cách này khi bạn thích cái tên ngắn r và chỉ muốn biến được gọi như vậy. Viết tên trường thật, dấu hai chấm, rồi tên biến của bạn: Shape::Circle { radius: r }. Hãy đọc nó là “lấy trường radius và gọi nó là r”.

enum Shape {
Circle { radius: f64 },
Rectangle { width: f64, height: f64 },
}
fn area(shape: &Shape) -> f64 {
match shape {
Shape::Circle { radius: r } => 3.14 * r * r, // ✅ trường `radius`, biến `r`
Shape::Rectangle { width: w, height: h } => w * h,
}
}
fn main() {
let small = Shape::Circle { radius: 1.0 };
let wide = Shape::Rectangle { width: 10.0, height: 2.5 };
println!("hình nhỏ: {:.2}", area(&small));
println!("hình rộng: {:.2}", area(&wide));
}

Kết quả:

hình nhỏ: 3.14
hình rộng: 25.00
▶ Tự chạy thử

Cách này cũng tiện khi tên trường sẽ trùng với các biến bạn đã có, hoặc khi tên trường quá dài.

Cách sửa 3: Kiểm tra xem bạn có đang so khớp đúng biến thể không

Phần tiêu đề “Cách sửa 3: Kiểm tra xem bạn có đang so khớp đúng biến thể không”

Chọn cách này khi trường bạn viết là có thật, chỉ có điều nó thuộc về một biến thể khác. Mỗi biến thể của enum có các trường riêng. Ở đây x thuộc về Click, nhưng nhánh lại so khớp KeyPress:

error[E0026]: variant `Event::KeyPress` does not have a field named `x`
--> src/main.rs:9:27
|
9 | Event::KeyPress { x, .. } => format!("key at {}", x), // ❌ `x` belongs to `Click`
| ^
| |
| variant `Event::KeyPress` does not have this field
| help: `Event::KeyPress` has a field named `key`

Chương trình đằng sau lỗi này:

enum Event {
Click { x: i32, y: i32 },
KeyPress { key: char },
}
fn describe(event: &Event) -> String {
match event {
Event::Click { x, y } => format!("click at ({}, {})", x, y),
Event::KeyPress { x, .. } => format!("key at {}", x), // ❌ `x` belongs to `Click`
}
}
fn main() {
println!("{}", describe(&Event::KeyPress { key: 'a' }));
}

Gợi ý của rustc (key) chỉ đơn giản là “trường duy nhất bạn chưa nhắc tới”, nên đừng làm theo một cách máy móc. Hãy tự hỏi nhánh này nên làm gì. Một lần nhấn phím thì không có vị trí, nên ở đây cách sửa đúng là dùng trường mà KeyPress thật sự có:

enum Event {
Click { x: i32, y: i32 },
KeyPress { key: char },
}
fn describe(event: &Event) -> String {
match event {
Event::Click { x, y } => format!("nhấp chuột tại ({}, {})", x, y),
Event::KeyPress { key } => format!("đã nhấn phím '{}'", key), // ✅ KeyPress có `key`
}
}
fn main() {
let events = [Event::Click { x: 10, y: 20 }, Event::KeyPress { key: 'a' }];
for event in &events {
println!("{}", describe(event));
}
}

Kết quả:

nhấp chuột tại (10, 20)
đã nhấn phím 'a'
▶ Tự chạy thử

Nếu một lần nhấn phím thật sự cần có vị trí, hãy thêm các trường vào biến thể: KeyPress { key: char, x: i32, y: i32 }. Khi đó mọi chỗ tạo ra một KeyPress cũng phải điền các trường này (rustc sẽ chỉ ra từng chỗ bằng E0063).

Sau khi đổi tên một trường. Bạn đổi r thành radius trong enum, và giờ mọi nhánh Shape::Circle { r } => ... đều báo E0026. Hãy tìm tên cũ trong cả dự án và cập nhật từng mẫu, hoặc dùng radius: r để giữ tên biến cũ.

Trong if let và let. if let Some(User { nmae, .. }) = find_user() cho ra cùng lỗi đó. Thêm .. không giúp được gì ở đây: .. bỏ qua những trường bạn không ghi tên, nhưng mọi tên bạn có viết vẫn phải tồn tại. Cách sửa: User { name, .. }.

Dùng ngoặc nhọn cho biến thể tuple hoặc tuple struct. Với struct Point(i32, i32);, mẫu Point { x, y } không có tên trường thật nào để so khớp. rustc báo điều đó bằng một mã khác, E0769, “tuple variant Point written as struct variant” (biến thể tuple Point bị viết như biến thể struct), và đề xuất Point(x, y). Xem Phân rã enum có dữ liệu.

Kiểm tra nhanh

1. Với enum Shape { Circle { radius: f64 } }, mẫu Shape::Circle { r } đòi lấy cái gì?

2. Bạn muốn lấy trường radius vào một biến tên r. Mẫu nào dùng được?

3. User { nmae, .. } có biên dịch được không nếu trường thật tên là name?

Điểm: 0 / 3