콘텐츠로 이동

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이에요.

어느 쪽이든 해결책은 같아요. 호출을 매개변수 목록에 맞추세요.

클로저의 매개변수 목록은 함수의 매개변수 목록만큼이나 고정되어 있어요. |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 here
help: 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)|를 쓰세요.

이해도 점검

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