Lỗi Rust E0424: expected value, found module `self` (dùng self ngoài phương thức)
E0424 nghĩa là bạn viết self bên trong một hàm không có tham số self, nên chẳng có “self” nào để Rust trỏ tới. Đây là một sơ suất rất hay gặp, nhất là khi bạn đến từ Java, C# hay JavaScript, nơi this lúc nào cũng có sẵn. Cách sửa thường chỉ là một từ: thêm &self vào danh sách tham số của hàm. Trang này thuộc hướng dẫn về lỗi trình biên dịch.
Thông báo lỗi
Phần tiêu đề “Thông báo lỗi”error[E0424]: expected value, found module `self` --> src/main.rs:7:32 |6 | fn describe() { | -------- this function doesn't have a `self` parameter7 | println!("Player: {}", self.name); // ❌ which player? | ^^^^ `self` value is a keyword only available in methods with a `self` parameter |help: add a `self` receiver parameter to make the associated `fn` a method |6 | fn describe(&self) { | +++++
error[E0599]: no method named `describe` found for struct `Player` in the current scope --> src/main.rs:13:10 |13 | hero.describe(); | ^^^^^^^^ this is an associated function, not a methodTrình biên dịch báo rằng self chỉ dùng được trong phương thức có tham số self, và gợi ý thêm &self vào describe.
Đây là chương trình gây ra lỗi:
struct Player { name: String,}
impl Player { fn describe() { println!("Player: {}", self.name); // ❌ which player? }}
fn main() { let hero = Player { name: String::from("Ferris") }; hero.describe();}Chú thích // ❌ which player? (người chơi nào?) giữ tiếng Anh vì nó xuất hiện trong thông báo lỗi. Lỗi thứ hai, E0599, là hệ quả của lỗi thứ nhất. Sửa E0424 là nó cũng biến mất.
Ý nghĩa
Phần tiêu đề “Ý nghĩa”Hãy nhìn hai nhãn mà rustc gắn lên code của bạn:
- Dòng 6:
fn describe()có cặp ngoặc rỗng. rustc ghi chú “this function doesn’t have aselfparameter” (hàm này không có tham sốself). - Dòng 7:
self.nameđòi tên của “người chơi mà hàm này được gọi trên đó”. Nhưngdescribekhông được trao người chơi nào, nênselfở đây chẳng có nghĩa gì.
Một hàm nằm trong khối impl mà không có tham số self là một hàm liên kết (associated function). Nó thuộc về kiểu (Player::describe()), không thuộc về một giá trị cụ thể nào. Một hàm có tham số self là một phương thức (method), và chỉ phương thức mới nói self được.
Hãy nghĩ tới một tờ đơn ghi “hãy viết tên của bạn vào đây”. Nó chỉ có nghĩa khi có ai đó đang cầm tờ đơn. describe() là tờ đơn chẳng ai cầm.
Vì sao nó lại nói “found module self”? Trong Rust, self còn có nghĩa thứ hai: module hiện tại, như trong use self::helpers;. Khi không có giá trị self nào, rustc quay về nghĩa đó rồi phàn nàn rằng một module không phải là giá trị. Bạn có thể bỏ qua chữ “module”; vấn đề thật là thiếu tham số.
Vì sao Rust làm vậy
Phần tiêu đề “Vì sao Rust làm vậy”Trong Rust, không có gì bị giấu. Java và C# cho mọi phương thức không static một this vô hình. Rust bắt bạn viết self làm tham số đầu tiên, và bạn còn nói rõ phương thức nhận nó theo cách nào: chỉ nhìn, thay đổi, hay lấy quyền sở hữu. Lựa chọn đó là một phần của quy tắc mượn (borrowing), nên nó phải hiện ra rõ ràng.
Nhờ vậy, mọi người đọc code của bạn, và cả trình biên dịch, biết chính xác một phương thức làm gì với giá trị của nó. fn summary(&self) hứa không thay đổi gì. fn add(&mut self, ...) báo trước rằng nó thay đổi. fn checkout(self) dùng hết giá trị. Rồi trình biên dịch kiểm tra các lời hứa đó cho bạn. Phương thức và khối impl được giải thích ở Struct.
Cách sửa
Phần tiêu đề “Cách sửa”Cách sửa 1: Thêm &self, &mut self hoặc self
Phần tiêu đề “Cách sửa 1: Thêm &self, &mut self hoặc self”Chọn cách này khi hàm được dùng để làm việc trên một giá trị đã có. Đây là cách sửa đúng trong phần lớn trường hợp. Thêm tham số mà rustc gợi ý và gọi bằng dấu chấm:
struct Player { name: String,}
impl Player { fn describe(&self) { println!("Người chơi: {}", self.name); }}
fn main() { let hero = Player { name: String::from("Ferris") }; hero.describe();}Kết quả:
Người chơi: Ferrisrustc luôn gợi ý &self, nhưng có ba loại. Hãy chọn loại khớp với việc phương thức làm:
| Tham số | Phương thức… | Tên thường gặp |
|---|---|---|
&self |
chỉ đọc giá trị | summary, len, is_empty, describe |
&mut self |
thay đổi giá trị | add, push, reset, level_up |
self |
dùng hết giá trị (lấy quyền sở hữu) | into_..., finish, build, checkout |
Đây là cả ba trên cùng một kiểu:
struct Order { items: Vec<String>,}
impl Order { fn summary(&self) -> String { format!("{} món: {}", self.items.len(), self.items.join(", ")) }
fn add(&mut self, item: &str) { self.items.push(item.to_string()); }
fn checkout(self) -> Vec<String> { self.items }}
fn main() { let mut order = Order { items: Vec::new() }; order.add("cà phê"); order.add("bánh mì"); println!("{}", order.summary()); let packed = order.checkout(); // order bị dùng hết ở đây println!("Đã đóng gói: {:?}", packed);}Kết quả:
2 món: cà phê, bánh mìĐã đóng gói: ["cà phê", "bánh mì"]Nếu bạn chọn &self mà phương thức lại thay đổi một trường, trình biên dịch sẽ báo ngay (E0594 “cannot assign to self.count, which is behind a & reference”, hoặc E0596 với các lời gọi như push). Hãy chuyển sang &mut self. Khi phân vân, cứ bắt đầu với &self; self là loại hiếm dùng nhất.
Cách sửa 2: Gọi bằng Type::method(&value)
Phần tiêu đề “Cách sửa 2: Gọi bằng Type::method(&value)”Một khi hàm đã có tham số self, bạn có thể gọi nó theo hai cách. hero.label() và Player::label(&hero) có nghĩa y hệt nhau: giá trị bạn đặt trước dấu chấm trở thành đối số đầu tiên. Cách dùng dấu chấm là cách bạn sẽ viết nhiều nhất. Cách Type:: tiện khi bạn muốn truyền chính phương thức cho thứ gì đó như map:
struct Player { name: String, level: u32,}
impl Player { fn label(&self) -> String { format!("{} (cấp {})", self.name, self.level) }}
fn main() { let hero = Player { name: String::from("Ferris"), level: 3 }; println!("{}", hero.label()); println!("{}", Player::label(&hero)); // cùng một lời gọi
let team = vec![hero, Player { name: String::from("Corro"), level: 5 }]; let labels: Vec<String> = team.iter().map(Player::label).collect(); println!("{:?}", labels);}Kết quả:
Ferris (cấp 3)Ferris (cấp 3)["Ferris (cấp 3)", "Corro (cấp 5)"]Thứ không chạy được là Player::label() mà không có giá trị: một phương thức luôn cần self của nó. Bỏ nó đi sẽ gây E0061, “this function takes 1 argument but 0 arguments were supplied” (hàm này nhận 1 đối số nhưng được truyền 0).
Cách sửa 3: Truyền vào thứ hàm cần
Phần tiêu đề “Cách sửa 3: Truyền vào thứ hàm cần”Chọn cách này khi chưa có giá trị nào, hoặc khi hàm hoàn toàn không nằm trong khối impl.
Trường hợp kinh điển là constructor. new tạo ra giá trị, nên chẳng có self nào để đọc. Hãy đưa dữ liệu ban đầu cho new qua các tham số bình thường và tạo struct bằng Self { ... }:
struct Counter { count: u32, step: u32,}
impl Counter { fn new(step: u32) -> Self { Self { count: 0, step } }
fn tick(&mut self) { self.count += self.step; }}
fn main() { let mut counter = Counter::new(5); counter.tick(); counter.tick(); println!("Đếm: {}", counter.count);}Kết quả:
Đếm: 10Để ý chữ hoa: Self (S hoa) là kiểu, nên Self { ... } tạo ra một giá trị mới. self (s thường) là giá trị mà phương thức được gọi trên đó.
Ý tưởng này cũng sửa được một hàm tự do, tức hàm nằm ngoài mọi khối impl. Ở đó rustc diễn đạt hơi khác: “this function can’t have a self parameter” (hàm này không thể có tham số self). Hãy truyền struct vào bằng tham chiếu:
struct Cart { items: Vec<u32>,}
fn total(cart: &Cart) -> u32 { cart.items.iter().sum()}
// gọi như sau: total(&cart)Hoặc chuyển total vào impl Cart và cho nó &self, như ở Cách sửa 1. Cả hai đều ổn; dùng phương thức sẽ đọc tự nhiên hơn khi hàm rõ ràng là “về” một giỏ hàng.
Thường gặp ở đâu
Phần tiêu đề “Thường gặp ở đâu”Bên trong new. Viết self.count = 0; trong fn new() -> Self gây E0424, và gợi ý fn new(&self) -> Self của rustc sẽ vô lý (bạn phải có một bộ đếm mới tạo được bộ đếm). Cách sửa: tạo giá trị bằng Self { count: 0 }, như ở Cách sửa 3.
Bên trong closure ở một hàm liên kết. Closure không phải là vấn đề; hàm bao quanh nó mới là vấn đề:
error[E0424]: expected value, found module `self` --> src/main.rs:8:35 |7 | fn sale_prices(prices: &[u32]) -> Vec<u32> { | ----------- this function doesn't have a `self` parameter8 | prices.iter().map(|p| p - self.discount).collect() | ^^^^ `self` value is a keyword only available in methods with a `self` parameterRust báo rằng sale_prices không có tham số self, nên closure bên trong không thể dùng self.discount. Cách sửa: biến nó thành phương thức, fn sale_prices(&self, prices: &[u32]), hoặc truyền con số vào, fn sale_prices(prices: &[u32], discount: u32). Xem Closure.
Trong main hoặc một hàm phụ. self không bao giờ dùng được bên ngoài khối impl. Cách sửa: truyền struct vào làm tham số (total(&cart)), hoặc gọi một phương thức trên nó (cart.total()).
Quen với phương thức “static” của C# hay Java. Một hàm liên kết không có self là phiên bản phương thức static của Rust. Nếu bạn muốn phiên bản dành cho đối tượng, bạn phải tự thêm tham số self. Xem Rust cho lập trình viên C#.
Lỗi liên quan
Phần tiêu đề “Lỗi liên quan”- E0425: sơ suất ngược lại. Bạn đang ở trong một phương thức, nhưng viết
countthay vìself.count. - E0599: “no method named … found”. Thường được in ngay sau E0424, vì một hàm không có
selfkhông thể gọi bằng dấu chấm. - E0061: sai số lượng đối số, ví dụ gọi
Player::label()mà không có giá trị. - E0596: bạn đã thêm
&self, nhưng phương thức thay đổi một trường, nên nó cần&mut self. Gán cho một trường theo cách này sẽ gây E0594. - Tất cả lỗi trình biên dịch: danh sách đầy đủ các trang lỗi.
Kiểm tra nhanh
Phần tiêu đề “Kiểm tra nhanh”Kiểm tra nhanh
1. Bên trong impl Player, fn heal() { self.hp += 10; } gây E0424. Cách sửa tốt nhất là gì?
2. Vì sao fn new() -> Self không dùng được self.name?
3. fn label(&self) -> String được định nghĩa trên Player. Lời gọi nào giống hệt hero.label()?
Điểm: 0 / 3
Đọc thêm
Phần tiêu đề “Đọc thêm”- E0424 — Rust error codes index (tiếng Anh): giải thích chính thức của lỗi này, kèm một ví dụ ngắn.
- Method Syntax — The Rust Book (tiếng Anh):
&self,&mut selfvàself, cùng các hàm liên kết nhưnew. - Associated functions and methods — Rust by Example (tiếng Anh): các ví dụ chạy được về cả hai loại, đặt cạnh nhau.
- Từ khóa
self— tài liệu thư viện chuẩn (tiếng Anh): cả hai nghĩa củaself, bên nhận của phương thức và module hiện tại. - Associated items — The Rust Reference (tiếng Anh): quy tắc chính xác cho phương thức, bên nhận (receiver) và hàm liên kết.