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

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

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
}

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

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.

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ái
Tổng: 600 xu
▶ Tự chạy thử

Nế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"]
▶ Tự chạy thử

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ột name, một ngày created_at.
  • Dùng phương thức khi giá trị được tính ra từ dữ liệu khác: total từ các món hàng, area từ chiều rộng và chiều cao, len từ 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 xu
▶ Tự chạy thử

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

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