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.)
Thông báo lỗi
Phần tiêu đề “Thông báo lỗi”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"), }; println!("{}", user.name); println!("{}", user.emial); // ❌ typo: the field is `email`}Ý nghĩa
Phần tiêu đề “Ý nghĩa”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ỗ.
Vì sao Rust làm vậy
Phần tiêu đề “Vì sao Rust làm vậy”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.
Cách sửa
Phần tiêu đề “Cách sửa”Cách sửa 1: Sửa lại chính tả
Phần tiêu đề “Cách sửa 1: Sửa lại chính tả”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"), }; println!("{}", user.name); println!("{}", user.email); // ✅ tên field thật}Kết quả:
FerrisNế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: FerrisKhông có người dùng nào có id 2Người dùng đầu tiên: AdaCá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.
Cách sửa 3: Dùng .0 và .1 cho tuple struct
Phần tiêu đề “Cách sửa 3: Dùng .0 và .1 cho tuple struct”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 = 4bình phương khoảng cách = 251920x1080Tuple 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!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.
Thường gặp ở đâu
Phần tiêu đề “Thường gặp ở đâu”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.
Lỗi liên quan
Phần tiêu đề “Lỗi liên quan”- E0599: no method named x found: phiên bản “phương thức” của lỗi này. Thường cùng nguyên nhân: gõ nhầm, hoặc một
Optioncần được mở ra. - E0425: cannot find value in this scope: biến trước dấu chấm hoàn toàn không tồn tại, hoặc bạn quên
self.trước một field. - E0308: mismatched types: lỗi bạn hay gặp tiếp theo nếu mở
Optionở chỗ này mà quên ở chỗ khác. E0615: attempted to take value of method: bạn viếtx.lenthay vìx.len().E0616: field is private: field có tồn tại nhưng khôngpub, nhưlentrênVec. Hãy dùng các phương thức của kiểu đó.E0560: struct has no field named x: cùng lỗi gõ nhầm, nhưng xảy ra khi tạo struct (User { emial: ... }).E0610: primitive type doesn't have fields:.somethingtrên một số,boolhoặcchar.- 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. 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
Đọc thêm
Phần tiêu đề “Đọc thêm”- E0609 — 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): field có tên, truy cập bằng dấu chấm và cú pháp cập nhật struct.
- Tuple Structs — The Rust Book (tiếng Anh): struct có field được truy cập theo vị trí, như
.0. - The Option Enum — The Rust Book (tiếng Anh): vì sao
Option<T>vàTlà hai kiểu khác nhau, và vì sao bạn phải mở nó ra. - Traits: Defining Shared Behavior — The Rust Book (tiếng Anh): phương thức của trait và ràng buộc trait, công cụ đúng cho code generic.