콘텐츠로 이동

Rust 에러 E0061: this function takes N arguments but M were supplied (인자 개수)

E0061은 함수를 잘못된 개수의 인자로 호출했다는 뜻이에요. 너무 적게 넘겼거나 너무 많이 넘긴 거죠. Rust에서 가장 친절한 에러 중 하나예요. 어떤 인자가 빠졌는지 또는 남는지, 그리고 그 인자의 타입이 무엇이어야 하는지 rustc가 정확히 알려 주거든요. 기본 인자와 오버로딩이 당연한 Python, JavaScript, C#에서 온 분들이 특히 자주 만나요. Rust에는 둘 다 없어요. 이 페이지에서 그 대신 무엇을 하면 되는지 보여 드릴게요. 이 시리즈의 다른 에러는 Rust 컴파일러 에러에서 볼 수 있어요.

error[E0061]: this function takes 2 arguments but 1 argument was supplied
--> src/main.rs:6:5
|
6 | greet("Ferris"); // ❌ greet는 인자가 두 개 필요해요
| ^^^^^---------- 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는 인자가 두 개 필요해요
| ++++++++++++

“이 함수는 인자를 2개 받는데 1개만 넘겼어요. &str 타입의 2번째 인자가 빠졌어요”라는 뜻이에요.

이 에러가 나는 프로그램은 이거예요.

fn greet(name: &str, greeting: &str) {
println!("{greeting}, {name}!");
}
fn main() {
greet("Ferris"); // ❌ greet는 인자가 두 개 필요해요
}

인자가 너무 많아도 같은 코드가 나와요. 이때는 “unexpected argument”(예상하지 못한 인자) 라벨과 “remove the extra argument”(남는 인자를 지우세요) 도움말이 붙어요.

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}`

점으로 호출한 메서드라면 첫 줄이 “this method takes…”(이 메서드는 …를 받는다)로 바뀌어요. Rgb(255, 0) 같은 튜플 구조체라면 “this struct takes 3 arguments”(이 구조체는 인자 3개를 받는다)라고 해요. 모두 같은 에러예요.

메시지는 세 부분으로 되어 있고, 하나하나가 도움이 돼요.

  1. 첫 줄은 개수를 세요. 함수는 인자 2개를 원하는데 1개를 넘겼어요.
  2. 호출 아래의 라벨은 문제를 짚어 줘요. “argument #2 of type &str is missing”(&str 타입의 2번째 인자가 빠졌다).
  3. note: 줄은 함수 정의로 가서, 빠뜨린 매개변수 greeting: &str에 밑줄을 그어요.

help: 줄은 인자가 들어갈 자리를 보여 줘요. /* &str */는 타입을 알려 주는 자리 표시일 뿐이에요. 주석이라서 그대로 붙여 넣으면 컴파일되지 않아요. 그 자리에 "안녕하세요" 같은 진짜 값을 넣으세요.

컵과 동전이 모두 있어야 하는 커피 자판기와 비슷해요. 동전만 넣는다고 커피가 반 잔 나오지는 않아요. 그냥 작동하지 않죠.

Rust에서 함수의 시그니처는 정확한 계약이에요. 이 매개변수들을, 이 순서로, 이 타입으로. 모든 호출이 여기에 맞아야 해요. 기본 인자(greeting = "Hello")도 없고, 오버로딩(매개변수만 다른 greet 함수 두 개)도 없고, 인자를 “몇 개든” 받는 함수도 없어요. 기본값을 적으려고 하면 error: parameter defaults are not supported(매개변수 기본값은 지원되지 않는다)가 나고, greet를 하나 더 만들면 E0428, “the name greet is defined multiple times”(greet라는 이름이 여러 번 정의되었다)가 나요.

엄격해 보이지만 그만한 보람이 있어요. greet(name, greeting)을 읽으면 어떤 함수가 실행되고 무엇을 받는지 정확히 알 수 있어요. 함수에 매개변수를 추가하면 컴파일러가 고쳐야 할 호출을 전부 알려 주니까, 빠뜨리는 곳이 생기지 않아요. (println!처럼 값을 몇 개든 받는 것들은 매크로예요. !가 바로 그 표시예요. 매크로를 참고하세요.)

매개변수가 어떻게 동작하는지는 함수에서 다뤄요.

방법 1: 시그니처가 요구하는 대로 정확히 넘기기

섹션 제목: “방법 1: 시그니처가 요구하는 대로 정확히 넘기기”

거의 언제나 이 방법을 고르세요. note:가 가리키는 곳을 열고, 매개변수 목록을 읽고, 매개변수마다 값을 하나씩 순서대로 넘기세요.

fn greet(name: &str, greeting: &str) {
println!("{greeting}, {name}!");
}
fn main() {
greet("Ferris", "안녕하세요");
greet("Corro", "좋은 아침이에요");
}

실행 결과:

안녕하세요, Ferris!
좋은 아침이에요, Corro!
▶ 직접 실행해 보기

함수를 막 바꿨더니 호출이 전부 깨졌다면, 그건 컴파일러가 도와주고 있는 거예요. 컴파일러가 알려 주는 호출을 하나씩 고치거나, 새 매개변수가 정말 선택 사항이어야 하는 건 아닌지 생각해 보세요(방법 3).

방법 2: 메서드는 점으로 호출하기

섹션 제목: “방법 2: 메서드는 점으로 호출하기”

함수에 self 매개변수가 있을 때 이 방법을 고르세요. 메서드는 보통 value.method(args)처럼 호출해요. 점 앞의 값이 self를 채우니까, 그건 세지 않아요. 대신 Type::method(args)라고 쓰면 self를 채워 주는 게 없어요.

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);
| +++++++++++++++++++

“&mut Account 타입의 1번째 인자가 빠졌어요”라는 뜻이에요.

개수를 세는 방식을 눈여겨보세요. Account::deposit(...)으로 호출하면 rustc는 함수가 인자 2개를 받는다고 해요. &mut self도 세기 때문이에요. acc.deposit()으로 호출하면 메서드가 인자 1개를 받는다고 해요. 점이 이미 self를 채워 주니까요. 인자를 다 채우면 두 형태 모두 동작해요.

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); // 보통 쓰는 방법
Account::deposit(&mut acc, 25); // 같은 호출을 풀어서 쓴 것
println!("잔액: {}", acc.balance);
}

실행 결과:

잔액: 75
▶ 직접 실행해 보기

평소 코드에서는 점 형태를 쓰세요. Type::method(&x, a)는 메서드 자체를 어딘가에 넘겨야 할 때 유용해요. team.iter().map(Player::label) 같은 경우죠(E0424의 예제). 함수에 self가 전혀 없다면(new처럼) Type::function(...)으로만 호출할 수 있어요. 구조체와 E0424를 참고하세요.

방법 3: 가끔만 필요한 인자에는 Option<T> 쓰기

섹션 제목: “방법 3: 가끔만 필요한 인자에는 Option<T> 쓰기”

인자 하나에 대개 적당한 기본값이 있을 때 이 방법을 고르세요. 그 인자를 Option으로 만들고, “기본값을 써 달라”는 뜻으로 None을 넘기고, unwrap_or로 채우세요.

fn connect(host: &str, port: u16, timeout_secs: Option<u64>) {
let timeout = timeout_secs.unwrap_or(30);
println!("{host}:{port}에 연결하는 중 (타임아웃 {timeout}초)");
}
fn main() {
connect("localhost", 8080, None);
connect("example.com", 443, Some(5));
}

실행 결과:

localhost:8080에 연결하는 중 (타임아웃 30초)
example.com:443에 연결하는 중 (타임아웃 5초)
▶ 직접 실행해 보기

솔직히 말하면, 호출하는 쪽은 여전히 None이라는 인자를 적어야 해요. Rust는 오히려 그걸 좋아해요. 기본값을 쓰고 있다는 게 호출에 드러나니까요. 아예 적고 싶지 않다면, 이름이 분명한 함수 두 개를 만드세요. 예를 들어 connect(host, port)가 connect_with_timeout(host, port, 30)을 호출하게 하는 거예요. 이게 오버로딩 대신 Rust에서 흔히 쓰는 답이에요. Option을 참고하세요.

방법 4: Default를 구현한 설정 구조체 쓰기

섹션 제목: “방법 4: Default를 구현한 설정 구조체 쓰기”

함수에 설정이 많은데 대부분의 호출은 한두 개만 바꿀 때 이 방법을 고르세요. 설정을 구조체에 넣고 Default를 구현한 다음, 호출하는 쪽은 ..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!(
"{}:{}에서 대기 중 (타임아웃 {}초, verbose: {})",
config.host, config.port, config.timeout_secs, config.verbose
);
}
fn main() {
start(ServerConfig::default());
start(ServerConfig { port: 3000, verbose: true, ..Default::default() });
}

실행 결과:

localhost:8080에서 대기 중 (타임아웃 30초, verbose: false)
localhost:3000에서 대기 중 (타임아웃 30초, verbose: true)
▶ 직접 실행해 보기

이렇게 하면 이름 붙은 인자 비슷한 것도 얻을 수 있어요. port: 3000을 보면 그 숫자가 무슨 뜻인지 알 수 있잖아요. 모든 필드의 기본값이 그 타입 자체의 기본값(0, false, 빈 문자열)이라면 #[derive(Default)]가 impl을 대신 써 줘요. 더 큰 API라면 빌더 패턴(Server::builder().port(3000).build())이 다음 단계예요. 구조체와 디자인 패턴을 참고하세요.

매개변수를 추가한 뒤. fn save(path: &str)에 두 번째 매개변수를 추가하면, 예전의 save("notes.txt") 호출이 모두 E0061로 깨져요. 해결: 컴파일러가 알려 주는 호출을 하나씩 고치거나, 새 값이 대개 같다면 방법 3이나 방법 4를 쓰세요.

표준 라이브러리 메서드. names.push("Cy", "Di")는 “this method takes 1 argument but 2 arguments were supplied”(이 메서드는 인자 1개를 받는데 2개를 넘겼다)를 내요. push는 아이템을 하나씩 추가해요. 해결: 두 번 호출하거나 names.extend(["Cy", "Di"])를 쓰세요. 벡터를 참고하세요.

튜플 구조체. struct Rgb(u8, u8, u8);에서 Rgb(255, 0)이라고 쓰면 “this struct takes 3 arguments but 2 arguments were supplied”(이 구조체는 인자 3개를 받는데 2개를 넘겼다)가 나요. 해결: 모든 필드를 채우세요. Rgb(255, 0, 0).

다른 언어에서 온 습관. Python의 greet("Ferris", greeting="Hi")는 Rust 문법이 아니에요. greet(greeting = "Hi")라고 쓰면 인자에 이름을 붙이는 게 아니에요. rustc는 이걸 greeting이라는 변수에 대입하는 걸로 읽고, E0425도 함께 알려 줘요. 해결: 값을 위치 순서대로 넘기거나, 설정 구조체를 쓰세요(방법 4).

클로저. add가 |a: i32, b: i32| a + b일 때 add(1)을 호출하면 같은 메시지가 나오지만, 코드는 E0057 (클로저 버전)이에요. 해결책도 같아요. 두 값을 모두 넘기세요.

  • E0308: 인자 개수는 맞는데 하나의 타입이 틀린 경우예요. 두 인자의 순서가 바뀐 경우가 많아요.
  • E0424: self 매개변수가 없는 함수에서 self를 쓴 경우로, 메서드와 연관 함수의 반대편 이야기예요.
  • E0599: 그런 이름의 메서드가 없는 경우예요. 예를 들어 new 같은 연관 함수를 점으로 호출할 때요.
  • E0425: 값을 찾을 수 없다는 에러로, 이름 붙은 인자를 써 보려고 할 때 E0061과 함께 나올 수 있어요.
  • 모든 컴파일러 에러: 에러 페이지 전체 목록이에요. 관련된 것으로 E0057(클로저를 잘못된 개수의 인자로 호출)과, 따로 페이지는 없지만 E0428(오버로딩하려다 함수를 두 번 정의)도 있어요.

이해도 점검

1. fn area(w: u32, h: u32) -> u32를 area(5)로 호출했어요. rustc는 뭐라고 할까요?

2. 메서드가 fn deposit(&mut self, amount: i64)예요. 올바른 호출은?

3. 대부분의 호출에서는 신경 쓰지 않는 timeout 인자를 만들고 싶어요. Rust에서 흔히 쓰는 방법은?

점수: 0 / 3