Lỗi Rust E0615: attempted to take value of method (dùng phương thức như field)
E0615 nghĩa là: bạn viết value.something mà không có dấu ngoặc, nhưng something là một phương thức (method), không phải field (trường). Trong Rust, một phương thức chỉ chạy khi bạn gọi nó bằng (), kể cả khi nó không nhận đối số nào. Lỗi này rất hay gặp nếu bạn đến từ C#, JavaScript hay Python, nơi .Count, .length hay một property trông như field bình thường. Cách sửa gần như luôn chỉ dài hai ký 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.)
Thông báo lỗi
Phần tiêu đề “Thông báo lỗi”error[E0615]: attempted to take value of method `len` on type `String` --> src/main.rs:3:46 |3 | println!("{} has {} letters", name, name.len); // ❌ `len` is a method | ^^^ method, not a field |help: use parentheses to call the method |3 | println!("{} has {} letters", name, name.len()); // ❌ `len` is a method | ++Trình biên dịch báo rằng bạn đang cố lấy giá trị của phương thức len trên kiểu String, và gợi ý thêm dấu ngoặc để gọi nó. Đây là chương trình gây ra lỗi:
fn main() { let name = String::from("Ferris"); println!("{} has {} letters", name, name.len); // ❌ `len` is a method}Ý nghĩa
Phần tiêu đề “Ý nghĩa”Hãy đọc dòng tiêu đề từ cuối lên: on type String là kiểu mà Rust đã xem xét, còn method len là thứ nó tìm thấy ở đó. Vậy là Rust có tìm thấy len. Chỉ có điều đó không phải một giá trị bạn đọc được như field. Đó là một đoạn code phải được chạy.
Nhãn method, not a field (phương thức, không phải field) và dấu ++ dưới () trong phần help nói đúng điều đó: thêm dấu ngoặc vào là xong.
Hãy nghĩ đến một máy bán hàng tự động. Field là một nhãn in trên máy (“Máy số 12”): bạn chỉ việc đọc. Phương thức là một cái nút (“Trả tiền thừa”): không có gì xảy ra cho đến khi bạn bấm. name.len chỉ vào cái nút. name.len() mới bấm nó.
Vì sao Rust làm vậy
Phần tiêu đề “Vì sao Rust làm vậy”Rust tách bạch field và phương thức. Một field là dữ liệu lưu bên trong struct. Một phương thức là một hàm được định nghĩa trong khối impl và nhận self. Bạn đọc field bằng value.field và gọi phương thức bằng value.method(). Xem Thêm hành vi bằng phương thức.
Nhiều ngôn ngữ làm mờ ranh giới này bằng property: list.Count trong C# hay text.length trong JavaScript trông như field nhưng chạy code ở phía sau. Rust cố ý không có property. Khi thấy (), bạn biết code sẽ chạy và có thể tốn thời gian; khi không thấy, bạn biết đó chỉ là đọc dữ liệu đã lưu. Không có gì bị che giấu.
Một struct thậm chí có thể có field và phương thức cùng tên, như một field riêng tư balance và một phương thức công khai balance(). Điều đó chỉ khả thi vì Rust không bao giờ đoán xem bạn muốn cái nào: không có ngoặc là field, có ngoặc là phương thức.
Cách sửa
Phần tiêu đề “Cách sửa”Cách sửa 1: Thêm dấu ngoặc
Phần tiêu đề “Cách sửa 1: Thêm dấu ngoặc”Gần như lúc nào cũng chọn cách này. Làm theo dòng help: và gọi phương thức. Cách này áp dụng như nhau cho phương thức của riêng bạn (như total trên giỏ hàng) và cho các phương thức chuẩn như len, is_empty, trim và count:
struct Item { price: u32, // tính bằng xu qty: u32,}
struct Cart { items: Vec<Item>,}
impl Cart { fn total(&self) -> u32 { self.items.iter().map(|i| i.price * i.qty).sum() }}
fn main() { let name = String::from("Ferris"); println!("{} có {} chữ cái", name, name.len()); // ✅ đã gọi
let cart = Cart { items: vec![Item { price: 250, qty: 2 }, Item { price: 100, qty: 1 }], }; if !cart.items.is_empty() { println!("Tổng: {} xu", cart.total()); // ✅ đã gọi }}Kết quả:
Ferris có 6 chữ cáiTổng: 600 xuNếu phương thức cần đối số, rustc chỉ chỗ đặt chúng bằng ký hiệu giữ chỗ _. Với s.contains, phần help ghi s.contains(_): hãy điền thứ bạn đang tìm, như s.contains("world"). Nếu bạn không chắc phương thức cần gì, lỗi tiếp theo của rustc (E0061) sẽ cho bạn biết nó nhận bao nhiêu đối số.
Cách sửa 2: Truyền phương thức như một hàm, không phải value.method
Phần tiêu đề “Cách sửa 2: Truyền phương thức như một hàm, không phải value.method”Chọn cách này khi bạn không định gọi phương thức ngay bây giờ, mà muốn đưa nó cho thứ khác, như .map() hay sort_by_key. Viết .map(words[0].len) cũng cho ra E0615. Bạn có hai lựa chọn tốt: một closure ngắn gọi phương thức, hoặc đường dẫn đầy đủ của phương thức, String::len, dùng được như tên hàm:
fn main() { let words = vec![String::from("crab"), String::from("ferris"), String::from("rust")];
// một closure gọi phương thức trên từng phần tử let lengths: Vec<usize> = words.iter().map(|w| w.len()).collect(); println!("{:?}", lengths);
// đường dẫn của phương thức dùng được như tên hàm let same: Vec<usize> = words.iter().map(String::len).collect(); println!("{:?}", same);
let mut sorted = words.clone(); sorted.sort_by_key(|w| w.len()); println!("{:?}", sorted);}Kết quả:
[4, 6, 4][4, 6, 4]["crab", "rust", "ferris"]Các từ ở đây được giữ tiếng Anh vì kết quả phụ thuộc vào độ dài byte của chúng. String::len dùng được ở đây vì iter() cho bạn các phần tử &String, và String::len nhận một &String. Khi các kiểu không khớp hoàn toàn, closure |w| w.len() là lựa chọn dễ dàng luôn chạy được.
Cách sửa 3: Biến nó thành field, nếu nó thật sự là dữ liệu lưu trữ
Phần tiêu đề “Cách sửa 3: Biến nó thành field, nếu nó thật sự là dữ liệu lưu trữ”Chọn cách này khi lỗi nằm trong struct của chính bạn và bạn nhận ra giá trị đó chỉ nên được lưu lại. Đây là câu hỏi thiết kế: field hay phương thức?
- Dùng field khi giá trị được đưa vào một lần và không phụ thuộc vào thứ gì khác: một
id, mộtname, một ngàycreated_at. - Dùng phương thức khi giá trị được tính ra từ dữ liệu khác:
totaltừ các món hàng,areatừ chiều rộng và chiều cao,lentừ nội dung.
Một field total được lưu sẵn sẽ lỗi thời ngay khi ai đó thêm một món và quên cập nhật nó. Một phương thức total() được tính lại mỗi lần, nên không bao giờ sai. Ở đây id là field còn total là phương thức, và bạn có thể thấy phương thức luôn đúng khi đơn hàng thay đổi:
mod shop { pub struct Order { pub id: u32, // lưu trữ: đặt một lần, đọc bằng order.id prices: Vec<u32>, // riêng tư: chỉ thay đổi qua phương thức }
impl Order { pub fn new(id: u32) -> Self { Order { id, prices: Vec::new() } }
pub fn add(&mut self, price: u32) { self.prices.push(price); }
// tính toán: luôn khớp với giá, không thể lỗi thời pub fn total(&self) -> u32 { self.prices.iter().sum() }
pub fn item_count(&self) -> usize { self.prices.len() } }}
use shop::Order;
fn main() { let mut order = Order::new(17); order.add(250); order.add(100); println!("Đơn hàng {}: {} món, {} xu", order.id, order.item_count(), order.total());
order.add(50); println!("Đơn hàng {}: {} món, {} xu", order.id, order.item_count(), order.total());}Kết quả:
Đơn hàng 17: 2 món, 350 xuĐơn hàng 17: 3 món, 400 xuCó một cách trung gian mà bạn sẽ thấy ở nhiều thư viện: một field riêng tư cộng với một phương thức công khai cùng tên (một “getter”). Code khác có thể đọc giá trị bằng acc.balance() nhưng không thể thay đổi trực tiếp. Khi đó viết acc.balance từ bên ngoài module sẽ cho ra một lỗi hơi khác, E0616 “field balance of struct Account is private”, kèm gợi ý a method `balance` also exists, call it with parentheses. Cách sửa vẫn vậy: thêm (). Xem Mọi thứ mặc định là riêng tư.
Thường gặp ở đâu
Phần tiêu đề “Thường gặp ở đâu”Thói quen từ ngôn ngữ khác. name.len, list.is_empty, text.trim, words.iter().count đều cho ra E0615. Tất cả chúng đều là phương thức trong Rust, nên đều cần (). Cách sửa: name.len(), list.is_empty(), v.v. Xem Các phương thức chuỗi hữu ích.
len trên một Vec. scores.len là ngoại lệ duy nhất của thông báo này: bạn nhận E0616, “field len of struct Vec is private”, vì Vec thật sự có một field ẩn tên len. rustc vẫn thêm help: a method `len` also exists, call it with parentheses. Cách sửa: scores.len(). (scores.is_empty cho ra E0615 bình thường.) Xem Vector.
Một phương thức thay đổi thứ gì đó. counter.increment; đứng một mình trên một dòng cũng cho ra E0615, dù bạn không cần giá trị nào. Không có () thì không có gì chạy. Cách sửa: counter.increment();.
Ngược lại: gọi field như phương thức. Nếu name là một field, user.name() cho ra E0599 với nhãn field, not a method và help: remove the arguments. Cách sửa: user.name. Còn nếu field chứa một closure, như on_click: Box<dyn Fn() -> String>, rustc nói surround the field access with parentheses: hãy viết (button.on_click)() để gọi nó.
Lỗi liên quan
Phần tiêu đề “Lỗi liên quan”- E0609: no field
xon typeT: cái tên sau dấu chấm không phải field cũng không phải phương thức của kiểu đó. Thường là lỗi gõ nhầm hoặc mộtOptioncần được mở ra. - E0599: no method named x found: chiều ngược lại,
user.name()khinamelà field, hoặc một phương thức không tồn tại. - E0616: field is private:
scores.lentrên mộtVec, hoặc một field riêng tư có getter công khai cùng tên. Hãy gọi phương thức. - E0061: wrong number of arguments: bạn đã thêm
(), nhưng phương thức cần đối số. - 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. Bạn nhận "attempted to take value of method is_empty on type String". Cách sửa là gì?
2. Cái nào thường nên là phương thức thay vì field lưu trữ?
3. Bạn muốn độ dài của từng từ: words.iter().map(???). Cái nào chạy được?
Điểm: 0 / 3
Đọc thêm
Phần tiêu đề “Đọc thêm”- E0615 — Rust error codes index (tiếng Anh): giải thích chính thức về lỗi này.
- Method Syntax — The Rust Book (tiếng Anh): cách định nghĩa và gọi phương thức, và các getter trùng tên với field.
- Defining and Instantiating Structs — The Rust Book (tiếng Anh): field, và cách đọc chúng bằng dấu chấm.
- Method call expressions — The Rust Reference (tiếng Anh): quy tắc chính xác cho
value.method(). - Methods — Rust by Example (tiếng Anh): các ví dụ ngắn chạy được về phương thức và hàm liên kết.