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

Lỗi Rust E0609: no field X on type T (kiểu không có field đó)

E0609 nghĩa là: bạn viết value.something, nhưng kiểu của value không có field (trường) nào tên là something. Phần lớn trường hợp đó là lỗi gõ nhầm, hoặc giá trị không hẳn có kiểu như bạn nghĩ (ví dụ một Option<User> thay vì một User). Thông báo lỗi cho bạn biết chính xác kiểu mà Rust nhìn thấy, và thường chừng đó là đủ để sửa. (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[E0609]: no field `emial` on type `User`
--> src/main.rs:12:25
|
12 | println!("{}", user.emial); // ❌ typo: the field is `email`
| ^^^^^ unknown field
|
help: a field with a similar name exists
|
12 - println!("{}", user.emial); // ❌ typo: the field is `email`
12 + println!("{}", user.email); // ❌ typo: the field is `email`
|

Trình biên dịch báo rằng kiểu User không có field emial và gợi ý field có tên gần giống là email. Đây là chương trình gây ra lỗi:

struct User {
name: String,
email: String,
}
fn main() {
let user = User {
name: String::from("Ferris"),
email: String::from("[email protected]"),
};
println!("{}", user.name);
println!("{}", user.emial); // ❌ typo: the field is `email`
}

Dòng tiêu đề có hai phần, và cả hai đều quan trọng:

  • no field `emial`: cái tên sau dấu chấm mà Rust không tìm thấy.
  • on type `User`: kiểu mà Rust nghĩ user đang có.

Vậy trước hết hãy kiểm tra cái tên, sau đó kiểm tra kiểu. Nếu kiểu trong thông báo không phải là struct bạn mong đợi, chẳng hạn Option<User>, &Option<User>, Vec<User> hay &T, thì cái tên không sai: vấn đề là bạn cần lấy struct ra trước đã.

Giống như bạn hỏi người bán hàng “cái màu xanh ở kệ thứ hai” trong khi đang đứng nhầm cửa hàng. Câu hỏi không sai, chỉ là bạn đang ở sai chỗ.

Trong Rust, các field của một struct được cố định ngay khi bạn viết struct đó. Trình biên dịch biết toàn bộ danh sách và kiểm tra mọi .field lúc biên dịch. Không có chuyện “tra lúc chạy rồi nhận về undefined” như JavaScript, hay AttributeError như Python: tên field sai không bao giờ lọt được vào chương trình của bạn. Xem Struct.

Các kiểu bọc như Option<T> và Result<T, E> là những kiểu riêng: struct của bạn nằm bên trong chúng, nhưng chúng không có các field của struct đó. Rust không tự với vào bên trong giúp bạn, vì có thể giá trị hoàn toàn không có ở đó. Bạn phải nói rõ chuyện gì xảy ra khi nó vắng mặt. Xem Option.

Code generic cũng theo đúng quy tắc chặt chẽ này. Trong fn greet<T>(x: &T), T có thể là bất kỳ kiểu nào, kể cả i32 hay bool, vốn không có field nào. Rust kiểm tra một hàm generic một lần cho mọi T có thể có, nên nó chỉ cho bạn dùng những gì mà ràng buộc trait (trait bound) hứa hẹn. Và trait chỉ có thể hứa hẹn phương thức (method), không hứa hẹn field. Xem Generic và Trait.

Chọn cách này khi kiểu trong thông báo đúng là struct bạn mong đợi. Hãy tìm dòng help: a field with a similar name exists; rustc thường biết chính xác bạn định viết field nào:

struct User {
name: String,
email: String,
}
fn main() {
let user = User {
name: String::from("Ferris"),
email: String::from("[email protected]"),
};
println!("{}", user.name);
println!("{}", user.email); // ✅ tên field thật
}

Kết quả:

▶ Tự chạy thử

Nếu không có gợi ý, hãy nhảy đến phần định nghĩa struct (trong hầu hết các editor: Ctrl+click hoặc F12 trên tên kiểu) và chép tên field từ đó. Tên field phân biệt chữ hoa chữ thường: userName và user_name là hai field khác nhau.

Cách sửa 2: Lấy struct ra khỏi Option trước

Phần tiêu đề “Cách sửa 2: Lấy struct ra khỏi Option trước”

Chọn cách này khi thông báo nói kiểu là Option<User>, Option<&User> hoặc &Option<User>. Chuyện này xảy ra rất thường xuyên với các hàm có thể không tìm thấy gì, và với các phương thức như .first(), .last(), .get() và .find():

error[E0609]: no field `name` on type `Option<User>`
--> src/main.rs:11:25
|
11 | println!("{}", user.name);
| ^^^^ unknown field
|
help: one of the expressions' fields has a field of the same name
|
11 | println!("{}", user.unwrap().name);
| +++++++++

Gợi ý .unwrap() biên dịch được, nhưng nó làm chương trình sập khi giá trị là None. Tốt hơn: hãy quyết định chuyện gì xảy ra trong cả hai trường hợp, bằng if let, match, hoặc map kèm một giá trị mặc định:

struct User {
name: String,
}
fn find_user(id: u32) -> Option<User> {
if id == 1 {
Some(User { name: String::from("Ferris") })
} else {
None
}
}
fn main() {
// if let: chỉ chạy code khi có một User
if let Some(user) = find_user(1) {
println!("Tìm thấy: {}", user.name);
}
// match: xử lý cả hai trường hợp
match find_user(2) {
Some(user) => println!("Tìm thấy: {}", user.name),
None => println!("Không có người dùng nào có id 2"),
}
// map + unwrap_or: lấy một field, hoặc dùng giá trị mặc định
let users = vec![User { name: String::from("Ada") }];
let first_name = users.first().map(|u| u.name.as_str()).unwrap_or("không có ai");
println!("Người dùng đầu tiên: {first_name}");
}

Kết quả:

Tìm thấy: Ferris
Không có người dùng nào có id 2
Người dùng đầu tiên: Ada
▶ Tự chạy thử

Các mẫu này cũng dùng được với một tham chiếu đến Option. Nếu maybe_user là &Option<User>, hãy viết if let Some(user) = maybe_user và user sẽ là &User. (Với &Option<User>, rustc không đưa ra gợi ý nào cả, nên điều này đáng để nhớ.) Bên trong một hàm trả về Option, let user = find_user(1)?; cũng dùng được. Xem Xử lý Option bằng match và if let.

Chọn cách này khi kiểu là một tuple struct, như struct Point(i32, i32);. Các field của nó không có tên, chỉ có vị trí, nên p.x cho ra no field `x` on type `Point` (kèm gợi ý p.0). Hãy dùng vị trí, hoặc tách nó ra thành các biến có tên. Nếu bạn muốn có tên, hãy biến nó thành struct với các field có tên:

struct Point(i32, i32); // tuple struct: các field là .0 và .1
struct Size {
width: u32, // field có tên: dùng .width và .height
height: u32,
}
fn main() {
let p = Point(3, 4);
println!("x = {}, y = {}", p.0, p.1);
let Point(x, y) = p; // hoặc tách nó ra thành các biến có tên
println!("bình phương khoảng cách = {}", x * x + y * y);
let screen = Size { width: 1920, height: 1080 };
println!("{}x{}", screen.width, screen.height);
}

Kết quả:

x = 3, y = 4
bình phương khoảng cách = 25
1920x1080
▶ Tự chạy thử

Tuple thường cũng hoạt động y như vậy. pair.2 trên một tuple hai phần tử cho ra E0609 kèm ghi chú available fields are: `0`, `1` (các field có sẵn là 0 và 1). Xem Truy cập phần tử theo vị trí.

Cách sửa 4: Dùng phương thức của trait thay cho field trên T generic

Phần tiêu đề “Cách sửa 4: Dùng phương thức của trait thay cho field trên T generic”

Chọn cách này khi thông báo ghi on type `&T` (hoặc T), kèm nhãn type parameter 'T' declared here. Code như sau sẽ lỗi, kể cả khi mọi kiểu bạn truyền vào đều có field name:

fn greet<T>(animal: &T) {
println!("Hello, {}!", animal.name); // ❌ no field `name` on type `&T`
}

Trait không thể đòi hỏi field, nhưng có thể đòi hỏi một phương thức getter. Hãy định nghĩa một trait nhỏ, cài đặt nó cho từng kiểu, rồi thêm nó làm ràng buộc cho T:

trait Named {
fn name(&self) -> &str;
}
struct Dog {
name: String,
}
struct Robot {
model: String,
}
impl Named for Dog {
fn name(&self) -> &str {
&self.name
}
}
impl Named for Robot {
fn name(&self) -> &str {
&self.model // field thậm chí không cần cùng tên
}
}
fn greet<T: Named>(thing: &T) {
println!("Xin chào, {}!", thing.name()); // gọi phương thức, không phải field
}
fn main() {
greet(&Dog { name: String::from("Rex") });
greet(&Robot { model: String::from("R2-D2") });
}

Kết quả:

Xin chào, Rex!
Xin chào, R2-D2!
▶ Tự chạy thử

Nếu hàm chỉ làm việc với đúng một kiểu, bạn không cần generic: viết fn greet(animal: &Dog) là animal.name dùng được ngay. Xem Trait làm tham số hàm.

Một phương thức thiếu (). Viết word.len hoặc cart.total (trong đó total là một phương thức) rất gần với E0609, nhưng rustc cho ra một mã khác: E0615, “attempted to take value of method”, kèm gợi ý “use parentheses to call the method” (hãy dùng dấu ngoặc để gọi phương thức). Trên một Vec, scores.len cho ra E0616, “field len of struct Vec is private”, vì Vec thật sự có một field ẩn với tên đó. Cả hai sửa giống nhau: word.len(), scores.len(), cart.total(). Còn "hello".length là E0609 bình thường trên &str: trong Rust nó tên là len(). Xem Chuỗi.

Một Vec thay vì một phần tử. dogs.name trên một Vec<Dog> cho ra no field `name` on type `Vec<Dog>`. Danh sách không có name, mỗi phần tử mới có. Cách sửa: chọn một phần tử bằng dogs[0].name, hoặc lặp với for dog in &dogs { println!("{}", dog.name); }. Xem Vector.

Một Result thay vì giá trị. load_config().port cho ra no field `port` on type `Result<Config, String>`, với cùng gợi ý .unwrap(). Cách sửa: let config = load_config()?; bên trong một hàm trả về Result, hoặc dùng match. Xem Toán tử ?.

Một field trên một con số. age.value trên một u32 lại là một mã khác: E0610, “u32 is a primitive type and therefore doesn’t have fields” (u32 là kiểu nguyên thủy nên không có field). Số, bool và char hoàn toàn không có field; có lẽ bạn định dùng một biến struct.

Kiểm tra nhanh

1. Lỗi báo không có field name trên kiểu Option<User>. Điều đó nói gì với bạn?

2. Làm sao đọc field đầu tiên của struct Meters(f64); từ một giá trị m?

3. Trong fn show<T>(x: &T), làm sao đọc được tên từ mọi T?

Điểm: 0 / 3