Lỗi Rust E0061: this function takes N arguments but M were supplied (sai số đối số)
E0061 nghĩa là bạn gọi một hàm với sai số lượng đối số (argument): quá ít hoặc quá nhiều. Đây là một trong những lỗi thân thiện nhất của Rust, vì rustc nói chính xác đối số nào bị thiếu hay bị thừa, và nó phải có kiểu gì. Lỗi này hay gặp với người đến từ Python, JavaScript hay C#, nơi đối số mặc định và nạp chồng hàm (overloading) là chuyện bình thường. Rust không có cả hai, và trang này cho bạn thấy nên làm gì thay thế. Các lỗi khác trong loạt bài này nằm ở 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[E0061]: this function takes 2 arguments but 1 argument was supplied --> src/main.rs:6:5 |6 | greet("Ferris"); // ❌ greet wants two arguments | ^^^^^---------- argument #2 of type `&str` is missing |note: function defined here --> src/main.rs:1:4 |1 | fn greet(name: &str, greeting: &str) { | ^^^^^ --------------help: provide the argument |6 | greet("Ferris", /* &str */); // ❌ greet wants two arguments | ++++++++++++Trình biên dịch báo rằng hàm nhận 2 đối số nhưng chỉ được truyền 1, và đối số thứ hai kiểu &str đang bị thiếu.
Đây là chương trình gây ra lỗi:
fn greet(name: &str, greeting: &str) { println!("{greeting}, {name}!");}
fn main() { greet("Ferris"); // ❌ greet wants two arguments}Chú thích // ❌ giữ tiếng Anh vì nó xuất hiện trong thông báo lỗi; nó nói rằng greet cần hai đối số.
Truyền quá nhiều đối số cho ra cùng mã lỗi, với nhãn “unexpected argument” (đối số không mong đợi) và dòng help “remove the extra argument” (bỏ đối số thừa):
error[E0061]: this function takes 2 arguments but 3 arguments were supplied --> src/main.rs:6:13 |6 | let a = area(3, 4, 5); | ^^^^ - unexpected argument #3 of type `{integer}`Với một phương thức gọi bằng dấu chấm, dòng đầu tiên sẽ ghi “this method takes…”. Với một tuple struct như Rgb(255, 0), nó ghi “this struct takes 3 arguments”. Lần nào cũng là cùng một lỗi.
Ý nghĩa
Phần tiêu đề “Ý nghĩa”Thông báo có ba phần, và phần nào cũng có ích:
- Dòng đầu tiên đếm số: hàm cần 2 đối số, bạn đưa 1.
- Nhãn dưới lời gọi của bạn nêu vấn đề: “argument #2 of type
&stris missing” (thiếu đối số thứ 2 kiểu&str). - Dòng
note:nhảy tới định nghĩa của hàm và gạch chân tham số bạn bỏ sót,greeting: &str.
Dòng help: cho thấy đối số cần đặt ở đâu. /* &str */ chỉ là chỗ giữ chỗ cho biết kiểu; nó là một chú thích, nên dán nguyên văn vào sẽ không biên dịch được. Hãy đặt một giá trị thật vào đó, như "Hello".
Giống như một máy pha cà phê cần cả cốc lẫn đồng xu. Chỉ bỏ đồng xu vào thì bạn không được nửa cốc cà phê. Máy đơn giản là không chạy.
Vì sao Rust làm vậy
Phần tiêu đề “Vì sao Rust làm vậy”Trong Rust, chữ ký (signature) của hàm là một hợp đồng chính xác: những tham số này, theo thứ tự này, với các kiểu này. Mọi lời gọi đều phải khớp với nó. Không có đối số mặc định (greeting = "Hello"), không có nạp chồng hàm (hai hàm cùng tên greet với tham số khác nhau), và không có hàm nào nhận “bao nhiêu đối số cũng được”. Cố viết giá trị mặc định sẽ gây error: parameter defaults are not supported, còn một greet thứ hai sẽ gây E0428, “the name greet is defined multiple times”.
Nghe thì khắt khe, nhưng đáng công. Khi đọc greet(name, greeting), bạn biết chính xác hàm nào chạy và nó nhận gì. Khi bạn thêm một tham số vào hàm, trình biên dịch liệt kê mọi lời gọi cần cập nhật, nên không sót chỗ nào. (Những thứ như println! nhận bao nhiêu giá trị cũng được là macro, đó là ý nghĩa của dấu !. Xem Macro.)
Cách tham số hoạt động được trình bày ở Hàm.
Cách sửa
Phần tiêu đề “Cách sửa”Cách sửa 1: Truyền đúng những gì chữ ký yêu cầu
Phần tiêu đề “Cách sửa 1: Truyền đúng những gì chữ ký yêu cầu”Chọn cách này gần như mọi lúc. Mở vị trí ghi trong note:, đọc danh sách tham số, và truyền mỗi tham số một giá trị, theo đúng thứ tự:
fn greet(name: &str, greeting: &str) { println!("{greeting}, {name}!");}
fn main() { greet("Ferris", "Xin chào"); greet("Corro", "Chào buổi sáng");}Kết quả:
Xin chào, Ferris!Chào buổi sáng, Corro!Nếu bạn vừa sửa hàm và giờ mọi lời gọi đều hỏng, đó là trình biên dịch đang giúp bạn: sửa từng lời gọi mà nó liệt kê, hoặc tự hỏi xem tham số mới có thật nên là tùy chọn không (Cách sửa 3).
Cách sửa 2: Gọi phương thức bằng dấu chấm
Phần tiêu đề “Cách sửa 2: Gọi phương thức bằng dấu chấm”Chọn cách này khi hàm có tham số self. Một phương thức thường được gọi dưới dạng value.method(args). Giá trị trước dấu chấm lấp vào chỗ self, nên bạn không đếm nó. Nếu bạn viết Type::method(args), chẳng có gì lấp vào self cả:
error[E0061]: this function takes 2 arguments but 1 argument was supplied --> src/main.rs:13:5 |13 | Account::deposit(50); | ^^^^^^^^^^^^^^^^ -- argument #1 of type `&mut Account` is missing |help: provide the argument |13 | Account::deposit(/* &mut Account */, 50); | +++++++++++++++++++Rust báo rằng đối số đầu tiên, kiểu &mut Account, đang bị thiếu. Để ý cách đếm. Khi gọi bằng Account::deposit(...), rustc nói hàm nhận 2 đối số, vì &mut self cũng được tính. Khi gọi bằng acc.deposit(), nó nói phương thức nhận 1 đối số, vì dấu chấm đã cung cấp self. Cả hai cách đều chạy được khi đủ mọi đối số:
struct Account { balance: i64,}
impl Account { fn deposit(&mut self, amount: i64) { self.balance += amount; }}
fn main() { let mut acc = Account { balance: 0 }; acc.deposit(50); // cách thông thường Account::deposit(&mut acc, 25); // cùng lời gọi đó, viết đầy đủ println!("Số dư: {}", acc.balance);}Kết quả:
Số dư: 75Trong code hằng ngày, hãy dùng cách dấu chấm. Type::method(&x, a) hữu ích khi bạn cần truyền chính phương thức đi đâu đó, như team.iter().map(Player::label) (ví dụ). Nếu một hàm hoàn toàn không có self (như new), nó chỉ có thể được gọi bằng Type::function(...). Xem Struct và E0424.
Cách sửa 3: Dùng Option<T> cho đối số đôi khi không cần
Phần tiêu đề “Cách sửa 3: Dùng Option<T> cho đối số đôi khi không cần”Chọn cách này khi một đối số thường có giá trị mặc định hợp lý. Hãy cho nó là Option, truyền None khi muốn “dùng mặc định”, và điền giá trị bằng unwrap_or:
fn connect(host: &str, port: u16, timeout_secs: Option<u64>) { let timeout = timeout_secs.unwrap_or(30); println!("Đang kết nối tới {host}:{port} (chờ tối đa {timeout}s)");}
fn main() { connect("localhost", 8080, None); connect("example.com", 443, Some(5));}Kết quả:
Đang kết nối tới localhost:8080 (chờ tối đa 30s)Đang kết nối tới example.com:443 (chờ tối đa 5s)Nói thật: nơi gọi vẫn phải viết đối số đó, None. Rust thích như vậy, vì lời gọi cho thấy rõ là đang dùng giá trị mặc định. Nếu bạn không muốn viết nó chút nào, hãy tạo hai hàm với tên rõ ràng, như connect(host, port) gọi connect_with_timeout(host, port, 30). Đó là câu trả lời quen thuộc của Rust thay cho nạp chồng hàm. Xem Option.
Cách sửa 4: Dùng struct cấu hình với Default
Phần tiêu đề “Cách sửa 4: Dùng struct cấu hình với Default”Chọn cách này khi một hàm có nhiều thiết lập và phần lớn nơi gọi chỉ đổi một hai cái. Đưa các thiết lập vào một struct, cài đặt Default, và để nơi gọi chỉ điền những gì họ cần bằng ..Default::default():
struct ServerConfig { host: String, port: u16, timeout_secs: u64, verbose: bool,}
impl Default for ServerConfig { fn default() -> Self { Self { host: String::from("localhost"), port: 8080, timeout_secs: 30, verbose: false, } }}
fn start(config: ServerConfig) { println!( "Đang lắng nghe trên {}:{} (chờ tối đa {}s, verbose: {})", config.host, config.port, config.timeout_secs, config.verbose );}
fn main() { start(ServerConfig::default()); start(ServerConfig { port: 3000, verbose: true, ..Default::default() });}Kết quả:
Đang lắng nghe trên localhost:8080 (chờ tối đa 30s, verbose: false)Đang lắng nghe trên localhost:3000 (chờ tối đa 30s, verbose: true)Cách này còn cho bạn thứ gần giống đối số có tên: port: 3000 nói rõ con số đó nghĩa là gì. Nếu giá trị mặc định của mọi trường đều là mặc định của chính kiểu đó (0, false, chuỗi rỗng), #[derive(Default)] sẽ viết impl giúp bạn. Với các API lớn hơn, builder pattern (Server::builder().port(3000).build()) là bước tiếp theo. Xem Struct và Mẫu thiết kế.
Thường gặp ở đâu
Phần tiêu đề “Thường gặp ở đâu”Sau khi thêm một tham số. Bạn thêm tham số thứ hai cho fn save(path: &str), và mọi lời gọi save("notes.txt") cũ đều hỏng với E0061. Cách sửa: cập nhật từng lời gọi mà trình biên dịch liệt kê, hoặc dùng Cách sửa 3 hay Cách sửa 4 nếu giá trị mới thường giống nhau.
Phương thức của thư viện chuẩn. names.push("Cy", "Di") cho ra “this method takes 1 argument but 2 arguments were supplied”. push chỉ thêm một phần tử. Cách sửa: gọi hai lần, hoặc dùng names.extend(["Cy", "Di"]). Xem Vector.
Tuple struct. struct Rgb(u8, u8, u8); rồi Rgb(255, 0) cho ra “this struct takes 3 arguments but 2 arguments were supplied”. Cách sửa: đưa đủ mọi trường, Rgb(255, 0, 0).
Thói quen từ ngôn ngữ khác. greet("Ferris", greeting="Hi") của Python không phải cú pháp Rust. Viết greet(greeting = "Hi") không đặt tên cho đối số; rustc đọc nó như một phép gán cho biến tên greeting và còn báo thêm E0425. Cách sửa: truyền giá trị theo vị trí, hoặc dùng struct cấu hình (Cách sửa 4).
Closure. Gọi add(1) khi add là |a: i32, b: i32| a + b cho ra cùng thông báo đó, nhưng dưới mã E0057 (phiên bản dành cho closure). Cách sửa cũng vậy: truyền đủ cả hai giá trị.
Lỗi liên quan
Phần tiêu đề “Lỗi liên quan”- E0308: số lượng đối số đúng, nhưng có một cái sai kiểu, thường do hai đối số bị đảo chỗ.
- E0424: dùng
selftrong một hàm không có tham sốself, mặt bên kia của chuyện phương thức và hàm liên kết. - E0599: không có phương thức nào tên đó, ví dụ gọi một hàm liên kết như
newbằng dấu chấm. - E0425: không tìm thấy giá trị, có thể xuất hiện cạnh E0061 khi bạn thử dùng đối số có tên.
- Tất cả lỗi trình biên dịch: danh sách đầy đủ các trang lỗi. Cũng liên quan: E0057 (closure được gọi với sai số đối số) và E0428 (một hàm được định nghĩa hai lần, khi cố nạp chồng), lỗi này không có trang riêng.
Kiểm tra nhanh
Phần tiêu đề “Kiểm tra nhanh”Kiểm tra nhanh
1. fn area(w: u32, h: u32) -> u32 được gọi bằng area(5). rustc báo gì?
2. Một phương thức là fn deposit(&mut self, amount: i64). Lời gọi nào đúng?
3. Bạn muốn một đối số timeout mà phần lớn nơi gọi không quan tâm. Cách làm thường gặp trong Rust là gì?
Điểm: 0 / 3
Đọc thêm
Phần tiêu đề “Đọc thêm”- E0061 — Rust error codes index (tiếng Anh): giải thích chính thức của lỗi này.
- Functions — The Rust Book (tiếng Anh): tham số, kiểu trong chữ ký hàm, và lời gọi hàm hoạt động thế nào.
- Method Syntax — The Rust Book (tiếng Anh): vì sao dấu chấm lấp vào
self, và các hàm liên kết nhưnew. - Trait
Default— tài liệu thư viện chuẩn (tiếng Anh): deriveDefaultvà dùng..Default::default(). - Builder pattern — Rust Design Patterns (tiếng Anh): cách thay thế phổ biến cho danh sách đối số dài và đối số tùy chọn.