Lỗi Rust E0057: this function takes N arguments but M were supplied (closure sai số đối số)
E0057 nghĩa là bạn gọi một closure với sai số lượng đối số: quá ít hoặc quá nhiều. Đây là phiên bản closure của E0061, và thông báo đọc lên gần như y hệt. Cách sửa thường rất nhanh: nhìn vào phần |...| của closure và truyền mỗi tham số một giá trị. Trang này cũng nói tới hai “họ hàng” gần mà bạn sẽ gặp khi dùng iterator, E0593 và E0631, vì chúng đến từ cùng một sự nhầm lẫn. 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[E0057]: this function takes 2 arguments but 1 argument was supplied --> src/main.rs:3:17 |3 | let total = add(1); // ❌ add wants two numbers | ^^^--- argument #2 of type `i32` is missing |note: closure defined here --> src/main.rs:2:15 |2 | let add = |a: i32, b: i32| a + b; | ^^^^^^^^^^^^^^^^help: provide the argument |3 | let total = add(1, /* i32 */); // ❌ add wants two numbers | +++++++++++Trình biên dịch báo rằng closure nhận 2 đối số nhưng chỉ được truyền 1, và đối số thứ hai kiểu i32 đang bị thiếu.
Đây là chương trình gây ra lỗi:
fn main() { let add = |a: i32, b: i32| a + b; let total = add(1); // ❌ add wants two numbers println!("{total}");}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 add cần hai con 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[E0057]: this function takes 1 argument but 2 arguments were supplied --> src/main.rs:3:5 |3 | greet("Ferris", "Corro"); | ^^^^^ ------- unexpected argument #2 of type `&'static str` |note: closure defined hereÝ nghĩa
Phần tiêu đề “Ý nghĩa”Hãy đọc thông báo giống như với hàm: dòng đầu tiên đếm số (closure nhận 2, bạn đưa 1), nhãn dưới lời gọi nêu đối số bị thiếu hay bị thừa, và help: cho thấy nó cần đặt ở đâu. /* i32 */ chỉ là một chú thích giữ chỗ cho biết kiểu. Hãy đặt một giá trị thật vào đó.
E0057 hay E0061? Dòng đầu tiên giống nhau. Dòng note: giúp phân biệt:
closure defined here: bạn đã gọi một closure. Đó là E0057.callable defined here: bạn đã gọi mộtfgeneric với ràng buộc nhưF: Fn(i32). Cũng là E0057.function defined here: mộtfnbình thường, một phương thức, hoặc một con trỏ hàm (let f: fn(i32, i32) -> i32 = add;). Đó là E0061.
Dù là lỗi nào, cách sửa cũng như nhau: cho lời gọi khớp với danh sách tham số.
Vì sao Rust làm vậy
Phần tiêu đề “Vì sao Rust làm vậy”Danh sách tham số của closure cũng cố định như của hàm. |a: i32, b: i32| nhận đúng hai i32, lần nào cũng vậy. Không có đối số mặc định và không có kiểu “muốn truyền ít hơn cũng được”. Ngay cả khi bạn bỏ qua kiểu, rustc cũng suy ra chúng từ thân closure và lần dùng đầu tiên, và từ đó trở đi chúng được khóa cứng.
Bên dưới, một closure cài đặt một trong các trait Fn với một danh sách kiểu đối số cố định, như Fn(i32, i32) -> i32. Trình biên dịch kiểm tra mọi lời gọi với danh sách đó, nên một lời gọi sai số lượng không bao giờ lọt vào chương trình đang chạy của bạn. Closure và tham số của nó hoạt động thế nào được trình bày ở Closure và Trait của closure.
Cách sửa
Phần tiêu đề “Cách sửa”Cách sửa 1: Cho lời gọi khớp với tham số của closure
Phần tiêu đề “Cách sửa 1: Cho lời gọi khớp với tham số của closure”Chọn cách này gần như mọi lúc. Nhảy tới vị trí ghi trong note:, đếm số tham số giữa hai dấu |...|, và truyền mỗi tham số một giá trị. Nếu bạn có nhiều giá trị hơn số tham số, hãy gọi closure nhiều lần:
fn main() { let add = |a: i32, b: i32| a + b; let greet = |name: &str| println!("Xin chào, {name}!");
let total = add(1, 2); println!("{total}");
greet("Ferris"); greet("Corro"); // mỗi tên một lần gọi}Kết quả:
3Xin chào, Ferris!Xin chào, Corro!Nếu closure thật sự nên nhận một số lượng giá trị khác, hãy sửa closure thay vào đó. Như vậy cũng ổn, miễn là mọi lời gọi đều khớp với nó.
Cách sửa 2: Tuple: một tuple là một đối số
Phần tiêu đề “Cách sửa 2: Tuple: một tuple là một đối số”Chọn cách này khi có tuple dính vào. |(name, score)| và |name, score| trông giống nhau, nhưng là hai closure khác nhau. Cái đầu nhận một đối số, một tuple, rồi mở nó ra. Cái sau nhận hai đối số.
Gọi một closure nhận tuple với hai giá trị sẽ gây E0057, và rustc gợi ý thêm cặp ngoặc:
error[E0057]: function takes 1 argument but 2 arguments were supplied --> src/main.rs:3:5 |3 | show("Ferris", 90); | ^^^^ |note: closure parameter defined herehelp: wrap these arguments in parentheses to construct a tuple |3 | show(("Ferris", 90)); | + +Rust gợi ý bọc hai đối số trong cặp ngoặc để tạo thành một tuple. Lỗi ngược lại xảy ra liên tục với iterator. zip, enumerate và việc lặp qua map đưa cho closure của bạn một tuple cho mỗi phần tử. Viết .map(|name, score| ...) sẽ cho ra một mã lỗi khác, E0593:
error[E0593]: closure is expected to take a single 2-tuple as argument, but it takes 2 distinct arguments --> src/main.rs:7:10 |7 | .map(|name, score| format!("{name}: {score}")) | ^^^ ------------- takes 2 distinct arguments | | | expected closure that takes a single 2-tuple as argument |help: change the closure to accept a tuple instead of individual arguments |7 - .map(|name, score| format!("{name}: {score}"))7 + .map(|(name, score)| format!("{name}: {score}"))Ở đây Rust nói closure lẽ ra phải nhận một tuple 2 phần tử, nhưng nó lại nhận hai đối số riêng. Cả hai lỗi có cùng cách sửa: đặt cặp ngoặc ở chỗ có tuple.
fn main() { let names = vec!["Ferris", "Corro"]; let scores = vec![90, 75];
// zip đưa ra một tuple (name, score) cho mỗi phần tử: mở nó ngay trong danh sách tham số let lines: Vec<String> = names .iter() .zip(scores.iter()) .map(|(name, score)| format!("{name}: {score}")) .collect(); println!("{:?}", lines);
// closure nhận một tuple thì được gọi với một tuple let show = |(name, score): (&str, i32)| println!("{name} được {score} điểm"); show(("Ferris", 90));}Kết quả:
["Ferris: 90", "Corro: 75"]Ferris được 90 điểm.enumerate().map(|(i, item)| ...) và map.iter().for_each(|(key, value)| ...) cũng vậy. Xem Iterator và Tuple.
Cách sửa 3: Chuyển đổi bằng một closure bọc ngoài
Phần tiêu đề “Cách sửa 3: Chuyển đổi bằng một closure bọc ngoài”Chọn cách này khi bạn truyền một hàm hay closure vào một hàm khác (“hàm bậc cao”, higher-order function) và số tham số không khớp. Ở đây apply muốn một thứ nhận một i32, nhưng add lại nhận hai. Điều đó lại gây E0593:
error[E0593]: function is expected to take 1 argument, but it takes 2 arguments --> src/main.rs:10:26 | 5 | fn add(a: i32, b: i32) -> i32 { | ----------------------------- takes 2 arguments...10 | println!("{}", apply(add, 5)); | ----- ^^^ expected function that takes 1 argument | | | required by a bound introduced by this callRust báo rằng apply cần một hàm nhận 1 đối số, nhưng add nhận 2. Đừng sửa add. Hãy bọc nó trong một closure nhỏ có đúng hình dạng mà apply cần và tự điền giá trị còn thiếu:
fn apply<F: Fn(i32) -> i32>(f: F, x: i32) -> i32 { f(x)}
fn add(a: i32, b: i32) -> i32 { a + b}
fn main() { let bonus = 10; // nhận 1 đối số, gọi add với 2 println!("{}", apply(|x| add(x, bonus), 5));
// nhận 1 tuple, gọi add với hai phần của nó let pairs = vec![(1, 2), (3, 4)]; let sums: Vec<i32> = pairs.into_iter().map(|(a, b)| add(a, b)).collect(); println!("{:?}", sums);}Kết quả:
15[3, 7]Closure bọc ngoài mượn bonus từ code xung quanh, đó đúng là việc closure làm giỏi nhất. Truyền một hàm bằng tên (apply(double, 5)) chỉ chạy được khi tham số của nó đã khớp sẵn. Xem Trait của closure.
Thường gặp ở đâu
Phần tiêu đề “Thường gặp ở đâu”Bên trong một hàm generic. fn call_twice<F: Fn(i32)>(f: F) mà gọi f(2, 3) sẽ gây E0057 với note: callable defined here, chỉ vào Fn(i32). Ràng buộc chính là danh sách tham số. Cách sửa: gọi f(2), hoặc đổi ràng buộc thành Fn(i32, i32) nếu bạn thật sự cần hai giá trị (khi đó closure của mọi nơi gọi đều phải nhận hai).
Closure được đóng hộp (boxed). let f: Box<dyn Fn(i32, i32) -> i32> = Box::new(|a, b| a + b); rồi f(1) gây E0057 với note: implementation defined here. Cách sửa: truyền đủ cả hai giá trị, f(1, 2).
Sắp xếp. v.sort_by(|a| a.cmp(a)) gây E0593, “closure is expected to take 2 arguments, but it takes 1 argument”. sort_by so sánh hai phần tử mỗi lần. Cách sửa: v.sort_by(|a, b| a.cmp(b)). Xem Sắp xếp.
Đúng số lượng, sai kiểu. nums.iter().map(|x: i32| x * 2) gây E0631, “type mismatch in closure arguments”, vì .iter() đưa ra &i32. Dòng help của rustc gợi ý |x: &i32|. Cách sửa đơn giản nhất: bỏ chú thích kiểu, |x| x * 2.
Mở một thứ không phải tuple. v.iter().map(|(a, b)| a + b) trên một Vec<i32> thường gây E0308, “expected integer, found (_, _)”. Cách sửa: chỉ dùng |(a, b)| khi các phần tử thật sự là cặp.
Lỗi liên quan
Phần tiêu đề “Lỗi liên quan”- E0061: wrong number of arguments: cùng thông báo đó cho hàm bình thường, phương thức và con trỏ hàm.
- E0308: mismatched types: số lượng đúng, nhưng một đối số (hoặc một mẫu tuple) sai kiểu.
- E0373: closure may outlive the current function: một lỗi closure phổ biến khác, về các biến được bắt trong luồng và trong closure được trả về.
- E0599: no method found: có thể xuất hiện cạnh E0631, vì một closure sai chữ ký làm hỏng phần còn lại của chuỗi iterator.
- E0593: closure takes 2 distinct arguments: một closure hoặc hàm được truyền vào với sai số tham số.
- E0631: type mismatch in closure arguments: đúng số tham số, nhưng sai kiểu.
- 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. let double = |x: i32| x * 2; được gọi bằng double(2, 3). rustc báo gì?
2. names.iter().zip(ages.iter()).map(|n, a| ...) không biên dịch được. Cách sửa là gì?
3. Cả E0057 lẫn E0061 đều nói this function takes 2 arguments but 1 argument was supplied. Làm sao biết bạn đã gọi một closure?
Điểm: 0 / 3
Đọc thêm
Phần tiêu đề “Đọc thêm”- E0057 — Rust error codes index (tiếng Anh): giải thích chính thức của lỗi này.
- Closures — The Rust Book (tiếng Anh): cú pháp closure, suy luận kiểu cho tham số, và các trait
Fn. - Closures — Rust by Example (tiếng Anh): các ví dụ ngắn chạy được về closure, kể cả truyền chúng vào hàm.
std::ops::Fn— tài liệu thư viện chuẩn (tiếng Anh): ràng buộcFn(i32) -> i32mô tả tham số của closure như thế nào.- E0593 — Rust error codes index (tiếng Anh): giải thích chính thức cho lỗi closure được truyền vào với sai số tham số.