Rust 에러 E0057: this function takes N arguments but M were supplied (클로저)
E0057은 클로저를 잘못된 개수의 인자로 호출했다는 뜻이에요. 너무 적게 넘겼거나 너무 많이 넘긴 거죠. E0061의 클로저 버전이고, 메시지도 거의 똑같아요. 해결은 대개 금방 끝나요. 클로저의 |...|를 보고 매개변수마다 값을 하나씩 넘기면 돼요. 이 페이지에서는 반복자를 쓸 때 만나는 가까운 친척 E0593과 E0631도 함께 다뤄요. 같은 착각에서 생기는 에러거든요. 이 시리즈의 다른 에러는 Rust 컴파일러 에러에서 볼 수 있어요.
에러 메시지
섹션 제목: “에러 메시지”error[E0057]: this function takes 2 arguments but 1 argument was supplied --> src/main.rs:3:17 |3 | let total = add(1); // ❌ add는 숫자 두 개가 필요해요 | ^^^--- 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는 숫자 두 개가 필요해요 | +++++++++++“이 함수는 인자를 2개 받는데 1개만 넘겼어요. i32 타입의 2번째 인자가 빠졌어요”라는 뜻이에요. note:는 클로저가 정의된 곳을, help:는 인자를 넣을 자리를 보여 줘요.
이 에러가 나는 프로그램은 이거예요.
fn main() { let add = |a: i32, b: i32| a + b; let total = add(1); // ❌ add는 숫자 두 개가 필요해요 println!("{total}");}인자가 너무 많아도 같은 코드가 나와요. 이때는 “unexpected argument”(예상하지 못한 인자) 라벨과 “remove the extra argument”(남는 인자를 지우세요) 도움말이 붙어요.
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무슨 뜻인가요
섹션 제목: “무슨 뜻인가요”함수의 경우처럼 읽으면 돼요. 첫 줄은 개수를 세고(클로저는 2개를 받는데 1개를 넘겼어요), 호출 아래의 라벨은 빠졌거나 남는 인자를 짚어 주고, help:는 인자가 들어갈 자리를 보여 줘요. /* i32 */는 타입을 알려 주는 자리 표시용 주석일 뿐이에요. 그 자리에 진짜 값을 넣으세요.
E0057일까요, E0061일까요? 첫 줄은 같아요. note:를 보면 구별할 수 있어요.
closure defined here(클로저가 여기 정의됨): 클로저를 호출한 거예요. E0057이에요.callable defined here(호출 가능한 것이 여기 정의됨):F: Fn(i32)같은 바운드가 있는 제네릭f를 호출한 거예요. 이것도 E0057이에요.function defined here(함수가 여기 정의됨): 일반fn, 메서드, 또는 함수 포인터(let f: fn(i32, i32) -> i32 = add;)를 호출한 거예요. 이건 E0061이에요.
어느 쪽이든 해결책은 같아요. 호출을 매개변수 목록에 맞추세요.
Rust가 이렇게 하는 이유
섹션 제목: “Rust가 이렇게 하는 이유”클로저의 매개변수 목록은 함수의 매개변수 목록만큼이나 고정되어 있어요. |a: i32, b: i32|는 언제나 정확히 i32 두 개를 받아요. 기본 인자도 없고, “원하면 덜 넘겨도 된다”는 것도 없어요. 타입을 생략해도 rustc가 클로저의 본문과 첫 사용에서 타입을 알아내고, 그 뒤로는 그 타입으로 고정돼요.
내부적으로 클로저는 Fn(i32, i32) -> i32처럼 인자 타입 목록이 정해진 Fn 트레이트 중 하나를 구현해요. 컴파일러는 모든 호출을 그 목록과 비교하니까, 개수가 틀린 호출은 실행되는 프로그램까지 절대 가지 않아요. 클로저와 매개변수가 어떻게 동작하는지는 클로저와 클로저 트레이트에서 다뤄요.
고치는 방법
섹션 제목: “고치는 방법”방법 1: 호출을 클로저의 매개변수에 맞추기
섹션 제목: “방법 1: 호출을 클로저의 매개변수에 맞추기”거의 언제나 이 방법을 고르세요. note:가 가리키는 곳으로 가서 파이프 |...| 사이의 매개변수를 세고, 하나마다 값을 하나씩 넘기세요. 값이 매개변수보다 많다면 클로저를 여러 번 호출하세요.
fn main() { let add = |a: i32, b: i32| a + b; let greet = |name: &str| println!("안녕하세요, {name}!");
let total = add(1, 2); println!("{total}");
greet("Ferris"); greet("Corro"); // 이름 하나에 호출 한 번}실행 결과:
3안녕하세요, Ferris!안녕하세요, Corro!클로저가 정말로 다른 개수의 값을 받아야 한다면, 클로저 쪽을 바꾸세요. 모든 호출이 클로저와 맞기만 하면 그것도 괜찮아요.
방법 2: 튜플은 튜플 하나가 인자 하나
섹션 제목: “방법 2: 튜플은 튜플 하나가 인자 하나”튜플이 끼어 있을 때 이 방법을 고르세요. |(name, score)|와 |name, score|는 비슷해 보이지만 다른 클로저예요. 앞의 것은 튜플 하나를 인자로 받아서 풀어요. 뒤의 것은 인자를 두 개 받아요.
튜플을 받는 클로저를 값 두 개로 호출하면 E0057이 나고, rustc는 괄호를 추가하라고 제안해요.
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)); | + +help:는 “이 인자들을 괄호로 감싸서 튜플을 만드세요”라는 뜻이에요.
반대 실수는 반복자에서 정말 자주 일어나요. zip, enumerate, 맵 순회는 클로저에 아이템마다 튜플 하나를 넘겨요. .map(|name, score| ...)라고 쓰면 다른 코드인 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}"))“클로저는 2-튜플 하나를 인자로 받아야 하는데, 서로 다른 인자 2개를 받고 있어요”라는 뜻이에요.
둘 다 해결책은 같아요. 튜플이 있는 자리에 괄호를 넣으세요.
fn main() { let names = vec!["Ferris", "Corro"]; let scores = vec![90, 75];
// zip은 아이템마다 (name, score) 튜플 하나를 줘요. 매개변수 목록에서 풀어요 let lines: Vec<String> = names .iter() .zip(scores.iter()) .map(|(name, score)| format!("{name}: {score}")) .collect(); println!("{:?}", lines);
// 튜플을 받는 클로저는 튜플 하나로 호출해요 let show = |(name, score): (&str, i32)| println!("{name}의 점수: {score}"); show(("Ferris", 90));}실행 결과:
["Ferris: 90", "Corro: 75"]Ferris의 점수: 90.enumerate().map(|(i, item)| ...)와 map.iter().for_each(|(key, value)| ...)도 마찬가지예요. 반복자와 튜플을 참고하세요.
방법 3: 감싸는 클로저로 모양 맞추기
섹션 제목: “방법 3: 감싸는 클로저로 모양 맞추기”함수나 클로저를 다른 함수(고차 함수, higher-order function)에 넘기는데 매개변수 개수가 맞지 않을 때 이 방법을 고르세요. 여기서 apply는 i32 하나를 받는 무언가를 원하는데, add는 두 개를 받아요. 그러면 또 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 call“함수는 인자 1개를 받아야 하는데, 2개를 받고 있어요”라는 뜻이에요.
add는 바꾸지 마세요. apply가 원하는 모양의 작은 클로저로 감싸고, 빠진 값은 그 클로저가 직접 채우게 하세요.
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; // 인자 1개를 받아서, add를 인자 2개로 호출해요 println!("{}", apply(|x| add(x, bonus), 5));
// 튜플 1개를 받아서, 두 부분으로 add를 호출해요 let pairs = vec![(1, 2), (3, 4)]; let sums: Vec<i32> = pairs.into_iter().map(|(a, b)| add(a, b)).collect(); println!("{:?}", sums);}실행 결과:
15[3, 7]감싸는 클로저는 주변 코드에서 bonus를 빌려 와요. 클로저가 가장 잘하는 일이죠. 함수를 이름으로 넘기는 방식(apply(double, 5))은 매개변수가 이미 맞을 때만 통해요. 클로저 트레이트를 참고하세요.
자주 보게 되는 곳
섹션 제목: “자주 보게 되는 곳”제네릭 함수 안에서. fn call_twice<F: Fn(i32)>(f: F) 안에서 f(2, 3)을 호출하면 note: callable defined here와 함께 E0057이 나고, Fn(i32)를 가리켜요. 바운드가 곧 매개변수 목록이에요. 해결: f(2)로 호출하거나, 정말 값 두 개가 필요하다면 바운드를 Fn(i32, i32)로 바꾸세요(그러면 호출하는 쪽의 모든 클로저가 인자 두 개를 받아야 해요).
박스에 담은 클로저. let f: Box<dyn Fn(i32, i32) -> i32> = Box::new(|a, b| a + b); 다음에 f(1)을 호출하면 note: implementation defined here와 함께 E0057이 나요. 해결: 두 값을 모두 넘겨요. f(1, 2).
정렬. v.sort_by(|a| a.cmp(a))는 E0593, “closure is expected to take 2 arguments, but it takes 1 argument”(클로저는 인자 2개를 받아야 하는데 1개를 받는다)를 내요. sort_by는 아이템을 두 개씩 비교하거든요. 해결: v.sort_by(|a, b| a.cmp(b)). 정렬을 참고하세요.
개수는 맞는데 타입이 틀릴 때. nums.iter().map(|x: i32| x * 2)는 E0631, “type mismatch in closure arguments”(클로저 인자의 타입 불일치)를 내요. .iter()는 &i32를 넘겨주기 때문이에요. rustc의 도움말은 |x: &i32|를 제안해요. 가장 간단한 해결: 타입 표기를 지우세요. |x| x * 2.
튜플이 아닌 걸 풀려고 할 때. 평범한 Vec<i32>에 v.iter().map(|(a, b)| a + b)를 쓰면 E0308, “expected integer, found (_, _)”(정수를 기대했는데 (_, _)가 왔다)가 나요. 해결: 아이템이 정말 쌍일 때만 |(a, b)|를 쓰세요.
관련 에러
섹션 제목: “관련 에러”- E0061: wrong number of arguments: 일반 함수, 메서드, 함수 포인터에서 나오는 같은 메시지예요.
- E0308: mismatched types: 개수는 맞는데 인자(또는 튜플 패턴)의 타입이 틀린 경우예요.
- E0373: closure may outlive the current function: 또 다른 흔한 클로저 에러로, 스레드와 반환되는 클로저에서 캡처한 변수에 관한 거예요.
- E0599: no method found: E0631과 같이 나올 수 있어요. 시그니처가 틀린 클로저가 반복자 체인의 나머지를 망가뜨리기 때문이에요.
- E0593: closure takes 2 distinct arguments: 매개변수 개수가 틀린 클로저나 함수를 넘긴 경우예요.
- E0631: type mismatch in closure arguments: 매개변수 개수는 맞는데 타입이 틀린 경우예요.
- 모든 컴파일러 에러: 에러 페이지 전체 목록이에요.
이해도 점검
섹션 제목: “이해도 점검”이해도 점검
1. let double = |x: i32| x * 2;를 double(2, 3)으로 호출했어요. rustc는 뭐라고 할까요?
2. names.iter().zip(ages.iter()).map(|n, a| ...)가 컴파일되지 않아요. 해결책은?
3. E0057과 E0061 모두 this function takes 2 arguments but 1 argument was supplied라고 해요. 클로저를 호출했다는 건 어떻게 알 수 있을까요?
점수: 0 / 3
더 알아보기
섹션 제목: “더 알아보기”- E0057 — Rust error codes index (영어): 이 에러에 대한 공식 설명이에요.
- 클로저 — The Rust Programming Language 한국어판: 클로저 문법, 매개변수의 타입 추론,
Fn트레이트를 다뤄요. - Closures — Rust by Example (영어): 클로저를 함수에 넘기는 것까지, 짧은 예제를 실행해 볼 수 있어요.
std::ops::Fn— 표준 라이브러리 문서 (영어):Fn(i32) -> i32같은 바운드가 클로저의 매개변수를 어떻게 나타내는지 설명해요.- E0593 — Rust error codes index (영어): 매개변수 개수가 틀린 클로저를 넘겼을 때 나는 에러의 공식 설명이에요.