콘텐츠로 이동

Rust 에러 E0107: takes N generic arguments but M were supplied (제네릭 인자 개수)

E0107은 <...> 안에 쓴 타입의 개수가 그 타입이나 함수가 기대하는 개수와 맞지 않는다는 뜻이에요. Result는 두 개(Result<T, E>), HashMap도 두 개(HashMap<K, V>), Option과 Vec는 하나를 원해요. 하나라도 모자라거나 남으면 rustc는 enum takes 2 generic arguments but 1 generic argument was supplied(열거형은 제네릭 인자 2개를 받는데 1개가 주어졌다) 같은 말을 해요. 앞부분에는 “struct”, “enum”, “type alias”, “function”, “method”가 와요. 가장 친절한 에러 중 하나예요. rustc가 두 숫자를 모두 알려 주고, 타입을 더하거나 빼기만 하면 되거든요. 이 페이지는 Rust 컴파일러 에러 해설의 일부예요.

error[E0107]: enum takes 2 generic arguments but 1 generic argument was supplied
--> src/main.rs:1:29
|
1 | fn parse_age(text: &str) -> Result<u32> { // ❌ `Result`에는 타입이 두 개 필요해요
| ^^^^^^ --- supplied 1 generic argument
| |
| expected 2 generic arguments
|
help: add missing generic argument
|
1 | fn parse_age(text: &str) -> Result<u32, E> { // ❌ `Result`에는 타입이 두 개 필요해요
| +++

help:는 “빠진 제네릭 인자를 추가하라”는 뜻이에요.

이 에러를 내는 프로그램은 다음과 같아요.

fn parse_age(text: &str) -> Result<u32> { // ❌ `Result`에는 타입이 두 개 필요해요
text.trim().parse()
}
fn main() {
for input in ["42", "마흔"] {
match parse_age(input) {
Ok(age) => println!("나이: {}", age),
Err(e) => println!("에러: {}", e),
}
}
}

rustc는 1번째 줄을 두 번 가리켜요.

  1. Result 아래의 ^^^^^^: “expected 2 generic arguments”(제네릭 인자 2개를 기대함). Result는 타입 매개변수가 두 개인 열거형이에요. 성공한 값을 위한 T와 에러를 위한 E요.
  2. u32 아래의 ---: “supplied 1 generic argument”(제네릭 인자 1개가 주어짐). T만 줬어요.
  3. help: 줄은 자리 표시용 E를 추가해요. 타입이 어디에 들어가는지 알려 주는 힌트일 뿐, 그 타입 자체는 아니에요. E를 진짜 에러 타입으로 바꿔야 해요.

제네릭 인자는 신청서의 빈칸과 비슷해요. Result 신청서에는 “성공하면:”, “실패하면:” 두 칸이 있어요. 한 칸만 채우면 담당자가 다시 돌려줘요.

Result<u32>처럼 타입을 하나만 쓴 코드를 본 적이 있다면 특히 자주 만나요. 그 코드가 틀린 건 아니에요. io::Result<T>나 anyhow::Result<T> 같은 타입 별칭(type alias)을 쓴 거예요. 이런 별칭은 에러 타입을 대신 채워 줘요. 프렐루드의 기본 Result에는 그런 지름길이 없어요.

제네릭 타입은 모든 매개변수에 값이 정해져야 완전한 타입이 돼요. E를 모르는 Result<u32, E>로는 값의 크기도, 에러가 어떻게 생겼는지도 알 수 없어서, Rust는 그걸 쓰는 코드를 컴파일할 수 없어요. Rust가 함수 본문을 보고 빠진 타입을 추측할 수도 있겠지만, 함수 시그니처에서는 일부러 절대 타입을 추론하지 않아요. 시그니처는 다른 코드가 믿고 쓰는 약속이라서 전부 적혀 있어야 해요.

기본값이 있는 매개변수도 있어요. HashMap<K, V, S = RandomState>에는 해시 함수를 위한 세 번째 매개변수가 있어서, rustc가 HashMap에 대해서는 “at least 2”(최소 2개)라고 말해요. 기본값은 끝에서부터만 채워지고, 타입을 만든 사람이 기본값을 넣어 둔 곳에서만 돼요. 제네릭과 Result를 참고하세요.

대부분은 이 방법을 고르세요. 빠진 매개변수가 무엇이어야 하는지 알아내서 쓰면 돼요. parse()의 경우, 숫자 파싱에 실패했을 때의 에러 타입은 std::num::ParseIntError예요.

use std::num::ParseIntError;
fn parse_age(text: &str) -> Result<u32, ParseIntError> {
text.trim().parse()
}
fn main() {
for input in ["42", "마흔"] {
match parse_age(input) {
Ok(age) => println!("나이: {}", age),
Err(e) => println!("에러: {}", e),
}
}
}

실행 결과:

나이: 42
에러: invalid digit found in string
▶ 직접 실행해 보기

“invalid digit found in string”은 Rust가 출력하는 에러 메시지로, “문자열에 올바르지 않은 숫자가 있다”는 뜻이에요. 어떤 에러 타입인지 모르겠다면, 내가 에러를 그대로 돌려주는 함수의 문서를 확인하세요. HashMap<String>도 같은 방법으로 고쳐요. HashMap<String, u32>처럼 값의 타입을 알려 주세요. 해시맵과 사용자 정의 에러를 참고하세요.

방법 2: 원래 의도한 타입 하나짜리 별칭(io::Result 등) 쓰기

섹션 제목: “방법 2: 원래 의도한 타입 하나짜리 별칭(io::Result 등) 쓰기”

파일, 네트워크, 표준 입력을 다룰 때, 또는 다른 사람 코드에서 Result<T>를 봤을 때 이 방법을 고르세요. std::io::Result<T>는 Result<T, std::io::Error>를 줄인 거예요. 모듈을 가져와서 io::Result라고 쓰세요.

use std::fs;
use std::io;
fn read_config(path: &str) -> io::Result<String> {
fs::read_to_string(path)
}
fn main() {
match read_config("missing-config.toml") {
Ok(text) => println!("{}", text),
Err(e) => println!("설정을 읽을 수 없어요: {}", e),
}
}

실행 결과:

설정을 읽을 수 없어요: No such file or directory (os error 2)
▶ 직접 실행해 보기

“No such file or directory”는 운영체제가 알려 주는 “그런 파일이나 디렉터리가 없다”는 메시지예요. anyhow 크레이트를 쓰는 앱은 같은 식으로 anyhow::Result<T>라고 써요. 반대 실수도 E0107이에요. io::Result<String, io::Error>라고 쓰면 type alias takes 1 generic argument but 2 generic arguments were supplied(타입 별칭은 제네릭 인자 1개를 받는데 2개가 주어졌다)가 나요. 별칭에 에러 타입이 이미 들어 있으니까요. 두 번째 것을 빼세요. 파일 입출력과 anyhow와 thiserror를 참고하세요.

방법 3: 함수를 제네릭으로 만들거나, 타입을 지정하기

섹션 제목: “방법 3: 함수를 제네릭으로 만들거나, 타입을 지정하기”

타입이 내가 만든 제네릭 구조체인데 이름만 썼을 때 이 방법을 고르세요. rustc는 missing generics for struct `Pair` (구조체 Pair의 제네릭이 빠졌다)라고 해요.

error[E0107]: missing generics for struct `Pair`
--> src/main.rs:6:13
|
6 | fn show(p: &Pair) { // ❌ 어떤 `K`와 `V`일까요?
| ^^^^ expected 2 generic arguments
|
note: struct defined here, with 2 generic parameters: `K`, `V`
--> src/main.rs:1:8
|
1 | struct Pair<K, V> {
| ^^^^ - -
help: add missing generic arguments
|
6 | fn show(p: &Pair<K, V>) { // ❌ 어떤 `K`와 `V`일까요?
| ++++++

note:는 “구조체가 여기 정의돼 있고, 제네릭 매개변수는 K, V 두 개다”라는 뜻이에요. 문제가 되는 프로그램은 이래요.

struct Pair<K, V> {
key: K,
value: V,
}
fn show(p: &Pair) { // ❌ 어떤 `K`와 `V`일까요?
println!("{} = {}", p.key, p.value);
}
fn main() {
show(&Pair { key: "음량", value: 7 });
show(&Pair { key: "테마", value: "다크" });
}

help:는 Pair<K, V>라고 써 주지만, show 안에는 아직 K와 V가 없으니 직접 선언도 해야 해요. show는 서로 다른 두 종류의 Pair로 호출되니까, 출력에 필요한 바운드와 함께 제네릭으로 만드세요.

use std::fmt::Display;
struct Pair<K, V> {
key: K,
value: V,
}
fn show<K: Display, V: Display>(p: &Pair<K, V>) {
println!("{} = {}", p.key, p.value);
}
fn main() {
show(&Pair { key: "음량", value: 7 });
show(&Pair { key: "테마", value: "다크" });
}

실행 결과:

음량 = 7
테마 = 다크
▶ 직접 실행해 보기

함수가 언제나 한 종류만 다룬다면, 대신 타입을 지정하세요. fn show(p: &Pair<&str, u32>)처럼요. impl Pair { ... }도 마찬가지로 impl<K, V> Pair<K, V> { ... }라고 써야 해요. 제네릭을 참고하세요.

rustc가 “but 2 generic arguments were supplied”(그런데 2개가 주어졌다)라며 더 적게 원할 때 이 방법을 고르세요. 함수 호출에 터보피시(turbofish) ::<>를 쓸 때 자주 생겨요.

error[E0107]: function takes 1 generic argument but 2 generic arguments were supplied
--> src/main.rs:13:20
|
13 | println!("{}", largest::<i32, i32>(&scores)); // ❌ 타입이 하나 더 많아요
| ^^^^^^^ ----- help: remove the unnecessary generic argument
| |
| expected 1 generic argument
|
note: function defined here, with 1 generic parameter: `T`
--> src/main.rs:1:4
|
1 | fn largest<T: PartialOrd + Copy>(items: &[T]) -> T {
| ^^^^^^^ -

help는 “필요 없는 제네릭 인자를 빼라”는 뜻이에요. 문제가 되는 프로그램은 이래요.

fn largest<T: PartialOrd + Copy>(items: &[T]) -> T {
let mut best = items[0];
for &item in items {
if item > best {
best = item;
}
}
best
}
fn main() {
let scores = [72, 95, 88];
println!("{}", largest::<i32, i32>(&scores)); // ❌ 타입이 하나 더 많아요
}

largest에는 매개변수가 T 하나뿐이라서 타입도 하나만 받아요. largest::<i32>(...)라고 쓰거나, 아예 터보피시를 빼세요. Rust가 인자를 보고 T를 알아낼 수 있거든요.

fn largest<T: PartialOrd + Copy>(items: &[T]) -> T {
let mut best = items[0];
for &item in items {
if item > best {
best = item;
}
}
best
}
fn main() {
let scores = [72, 95, 88];
println!("{}", largest(&scores));
println!("{}", largest::<f64>(&[1.5, 0.5]));
}

실행 결과:

95
1.5
▶ 직접 실행해 보기

여기서 가장 쓸모 있는 줄은 note:예요. 정의를 보여 주고 매개변수 목록을 알려 주니까, 직접 개수를 세어 볼 수 있어요.

키만 쓴 HashMap<String>. rustc는 struct takes at least 2 generic arguments but 1 generic argument was supplied(구조체는 제네릭 인자를 최소 2개 받는데 1개가 주어졌다)라고 해요. 해결: HashMap<String, u32>라고 쓰거나, HashMap::new()라고 쓰고 첫 insert를 보고 Rust가 둘 다 추론하게 하세요. 해시맵을 참고하세요.

타입 매개변수가 두 개인 내 구조체에 하나만 준 경우. let p: Pair<&str> = ...는 struct takes 2 generic arguments but 1 generic argument was supplied를 내요. 해결: Pair<&str, i32>, 또는 둘 다 Rust가 채우게 Pair<_, _>라고 쓰세요. Option<i32, i32>는 그 반대예요. enum takes 1 generic argument but 2 generic arguments were supplied가 나요. 값 두 개를 원했다면 튜플 Option<(i32, i32)>를 쓰세요.

라이프타임도 개수에 들어가요. 구조체 Excerpt<'a>를 Excerpt<'a, 'a>라고 쓰면 struct takes 1 lifetime argument but 2 lifetime arguments were supplied(라이프타임 인자 1개를 받는데 2개가 주어졌다)가 나요. 해결: 정의에 있는 매개변수마다 라이프타임을 하나씩만 쓰세요. 라이프타임을 참고하세요. .into::<String>()처럼 아예 제네릭이 아닌 메서드에 쓰면 method takes 0 generic arguments but 1 generic argument was supplied가 나요. 대신 let s: String = x.into();라고 쓰세요(E0283 참고).

놀라운 점이 하나 있어요. Vec<i32, i32>는 E0107이 아니에요. Vec에는 메모리 할당자를 위한 불안정한(unstable) 두 번째 매개변수가 있어서, 대신 E0658(영어, allocator_api)과 몇 가지 다른 에러가 나요. 고치는 법은 같아요. Vec<i32>, 짝을 담으려면 Vec<(i32, i32)>라고 쓰세요.

  • E0061: 일반 인자에서 하는 같은 실수예요. 함수가 인자 N개를 받는데 M개가 주어졌어요.
  • E0106: 라이프타임 지정자가 빠졌어요. 타입 안의 참조자에 'a가 필요할 때예요.
  • E0252: use 줄 두 개가 같은 이름을 가져와요. Result가 fmt::Result를 가리키게 되면 E0107이 뒤따르는 경우가 많아요.
  • E0283: 타입 표기가 필요해요. .into::<T>()가 E0107을 낼 때의 올바른 해결책이에요.
  • E0401: 안쪽 함수가 바깥 함수의 제네릭 T를 써요.
  • 전체 컴파일러 에러: 에러 페이지 전체 목록이에요.

이해도 점검

1. fn load() -> Result<String>에서 E0107이 나요. 파일을 읽는 함수라면 어떤 반환 타입으로 고칠까요?

2. rustc가 HashMap에 대해 at least 2 generic arguments(최소 2개)라고 하는 이유는 무엇일까요?

3. fn show(p: &Pair)에서 `missing generics for struct Pair 가 나고, show는 여러 종류의 Pair`로 호출돼요. 어떻게 고칠까요?

점수: 0 / 3