Rust 에러 E0631: type mismatch in closure arguments (클로저 인자 타입 불일치)
E0631은 클로저의 매개변수 개수는 맞지만, 적어도 하나의 타입이 틀렸다는 뜻이에요. 열에 아홉은 & 하나 차이예요. |x: i32|라고 썼는데, 반복자는 &i32를 빌려주는 거죠. 가장 빠른 해결책은 대개 타입 명시를 지우고 Rust가 알아서 정하게 두는 거예요. 이 페이지에서는 그 방법을 먼저 보여 주고, 그다음에 &가 어디서 오는지 설명할게요. 그러면 다음번에는 미리 알아챌 수 있을 거예요. 이 시리즈의 다른 에러는 Rust 컴파일러 에러 해설에서 볼 수 있어요.
에러 메시지
섹션 제목: “에러 메시지”error[E0631]: type mismatch in closure arguments --> src/main.rs:3:43 |3 | let doubled: Vec<i32> = prices.iter().map(|p: i32| p * 2).collect(); // ❌ 아이템은 &i32예요 | ^^^ -------- found signature defined here | | | expected due to this | = note: expected closure signature `fn(&{integer}) -> _` found closure signature `fn(i32) -> _`note: required by a bound in `map`help: consider adjusting the signature so it borrows its argument |3 | let doubled: Vec<i32> = prices.iter().map(|p: &i32| p * 2).collect(); // ❌ 아이템은 &i32예요 | +“type mismatch in closure arguments”는 “클로저 인자의 타입이 맞지 않는다”는 뜻이에요. note는 기대한 시그니처와 실제로 찾은 시그니처를 나란히 보여 주고, help는 “인자를 빌려 받도록 시그니처를 고쳐 보라”고 제안해요.
이 에러를 내는 프로그램은 다음과 같아요.
fn main() { let prices = vec![120, 45, 300]; let doubled: Vec<i32> = prices.iter().map(|p: i32| p * 2).collect(); // ❌ 아이템은 &i32예요 println!("{:?}", doubled);}rustc는 바로 뒤에 더 긴 에러를 하나 더 출력해요. the method `collect` exists for struct `Map<...>`, but its trait bounds were not satisfied(collect 메서드는 있지만 트레이트 바운드를 만족하지 않음, E0599)예요. 이건 부수 효과일 뿐이에요. E0631을 고치면 사라져요.
무슨 뜻인가요
섹션 제목: “무슨 뜻인가요”두 note 줄이 메시지의 핵심이에요. “메서드는 이걸 원했는데, 나는 저걸 썼다”로 읽으면 돼요.
- expected
fn(&{integer}) -> _:map은 정수의 참조자로 클로저를 호출할 거예요.{integer}는 “Rust가 아직 정하지 않은 어떤 정수 타입”이고,_는 “아무 반환 타입”이라는 뜻이에요. - found
fn(i32) -> _: 내 클로저는i32를 값으로 받겠다고 했어요.
나란히 놓고 보면 다른 건 & 하나뿐이에요. help: 줄이 그 한 글자짜리 해결책을 보여 줘요.
옷 보관소를 떠올려 보세요. 코트를 맡기면 번호표를 받죠. .iter()도 똑같아요. 아이템 자체를 건네주는 게 아니라, 아이템의 번호표(참조자)를 건네줘요. 코트 자체만 받겠다고 고집하는 클로저는 번호표를 받아 주지 않아요.
Rust가 이렇게 하는 이유
섹션 제목: “Rust가 이렇게 하는 이유”반복자 메서드는 모두 클로저에 정확히 어떤 타입을 넘길지 정해 두고 있고, 그 타입은 반복자를 어떻게 만들었는지에 따라 달라져요.
v.iter()는 아이템을 하나씩 빌려줘요:&T.v.into_iter()(내가 소유한Vec에서)는 아이템을 하나씩 넘겨줘요:T.filter와find는 아이템을 한 번 더 빌려줘요. 그래서.iter()위에서는&&T가 돼요.sort_by는 아이템 두 개를 빌려줘요:&T와&T.
매개변수 타입을 생략하면 Rust가 메서드를 보고 채워 넣으니까, 언제나 딱 맞아요. 타입을 직접 쓰면 Rust는 내 말을 그대로 믿고 검사해요. 몰래 값을 복사하거나 참조자를 소유한 값으로 바꿔 주지는 않아요. 그러면 코드가 하는 일이 달라지기 때문이에요(String이라면 복사에 메모리와 시간이 들어요). 반복자를 얻는 세 가지 방법은 반복자 강좌에서, 참조자는 대여에서 설명해요.
고치는 방법
섹션 제목: “고치는 방법”방법 1: 타입 명시 지우기
섹션 제목: “방법 1: 타입 명시 지우기”먼저 이 방법을 고르세요. 클로저 매개변수에는 타입이 거의 필요 없어요. map, filter, sort_by가 무엇을 넘기는지 Rust가 정확히 알거든요. : i32를 지우면 컴파일돼요.
fn main() { let prices = vec![120, 45, 300]; let ages = vec![12, 34, 17, 50]; let mut scores = vec![42, 7, 99, 15];
// 타입 명시 없음: Rust가 반복자가 실제로 주는 타입을 써요 let doubled: Vec<i32> = prices.iter().map(|p| p * 2).collect(); let adults: Vec<&i32> = ages.iter().filter(|age| **age >= 18).collect(); scores.sort_by(|a, b| b.cmp(a));
println!("{:?}", doubled); println!("{:?}", adults); println!("{:?}", scores);}실행 결과:
[240, 90, 600][34, 50][99, 42, 15, 7]p * 2가 &i32에서도 동작하는 건, Rust의 숫자 연산자가 참조자도 받기 때문이에요. filter 안에서 age는 &&i32라서, **age로 참조자 두 개를 따라가야 숫자에 닿아요. b.cmp(a) 같은 비교는 참조자에서 바로 동작해요.
방법 2: 메서드가 실제로 넘기는 타입 쓰기
섹션 제목: “방법 2: 메서드가 실제로 넘기는 타입 쓰기”긴 체인을 읽기 쉽게 하려는 등의 이유로 타입을 눈에 보이게 남기고 싶을 때 이 방법을 고르세요. rustc의 help:를 따라 &를 붙이면 돼요. &를 패턴에 넣을 수도 있어요. |&p|는 참조자를 풀어서 숫자의 평범한 복사본을 줘요(정수 같은 Copy 타입에서 통해요).
fn main() { let prices = vec![120, 45, 300]; let ages = vec![12, 34, 17, 50]; let mut scores = vec![42, 7, 99, 15];
// 진짜 타입을 써요: .iter()는 &, filter 안에서는 && let doubled: Vec<i32> = prices.iter().map(|p: &i32| p * 2).collect(); let adults: Vec<&i32> = ages.iter().filter(|age: &&i32| **age >= 18).collect(); scores.sort_by(|a: &i32, b: &i32| b.cmp(a));
// 또는 & 패턴으로 숫자를 복사해서 꺼내요 let tax: Vec<i32> = prices.iter().map(|&p| p / 10).collect();
println!("{:?}", doubled); println!("{:?}", adults); println!("{:?}", scores); println!("{:?}", tax);}실행 결과:
[240, 90, 600][34, 50][99, 42, 15, 7][12, 4, 30]차이를 눈여겨보세요. |p: &i32|는 “p는 참조자예요“라는 뜻이고, |&p|는 “참조자를 받을 테니, 그 뒤에 있는 값을 주세요”라는 뜻이에요.
방법 3: 반복자가 넘겨주는 것 바꾸기
섹션 제목: “방법 3: 반복자가 넘겨주는 것 바꾸기”참조자가 아니라 정말로 값이 필요할 때 이 방법을 고르세요. 값을 얻는 방법은 두 가지예요.
.iter().copied()는&i32를 하나하나i32로 바꿔요. 숫자,char,bool같은 작은Copy타입에 쓰세요. (.cloned()는String같은 타입에서 복제를 해서 같은 일을 해요.)Vec에서.into_iter()를 쓰면 아이템 자체를 넘겨줘요. 그 뒤로Vec은 다 써 버린 상태가 되니, 더 필요 없을 때 고르세요.
fn main() { let prices = vec![120, 45, 300];
// .copied()는 &i32 아이템을 i32 아이템으로 바꿔요 let doubled: Vec<i32> = prices.iter().copied().map(|p: i32| p * 2).collect(); println!("{:?}", doubled);
// Vec의 .into_iter()는 아이템 자체를 넘겨줘요 (그리고 Vec을 다 써 버려요) let names = vec![String::from("ferris"), String::from("corro")]; let loud: Vec<String> = names.into_iter().map(|name: String| name.to_uppercase()).collect(); println!("{:?}", loud);}실행 결과:
[240, 90, 600]["FERRIS", "CORRO"]반복이 끝난 뒤에도 names가 필요하다면 .iter()를 그대로 두고 &String을 받으세요. 빌리기만 한 Vec에서 값을 꺼내 가려고 하면 다른 에러인 E0507이 나와요.
방법 4: 이름 있는 함수가 반복자가 주는 타입을 받게 하기
섹션 제목: “방법 4: 이름 있는 함수가 반복자가 주는 타입을 받게 하기”.map(shout)처럼 함수를 이름으로 넘길 때 이 방법을 고르세요. 그러면 메시지에 “closure” 대신 “function”이라고 나와요.
error[E0631]: type mismatch in function arguments --> src/main.rs:7:51 |1 | fn shout(word: &String) -> String { | --------------------------------- found signature defined here...7 | let loud: Vec<String> = words.into_iter().map(shout).collect(); | --- ^^^^^ expected due to this | | | required by a bound introduced by this call | = note: expected function signature `fn(&str) -> _` found function signature `fn(&String) -> _`“함수 인자의 타입이 맞지 않는다”는 뜻이에요. note를 보면 map은 fn(&str)을 기대했는데, 찾은 건 fn(&String)이에요.
아이템은 &str인데, shout는 &String을 원해요. 좋은 습관이 하나 있어요. 텍스트를 읽기만 하는 함수는 &str을 받게 하세요. 그러면 문자열 리터럴, &String 등을 모두 받을 수 있어요. filter처럼 반복자가 &를 하나 더 붙일 때는, 호출을 짧은 클로저로 감싸세요. 평범한 함수 호출 안에서는 Rust가 남는 &를 알아서 벗겨 주지만(이걸 역참조 강제 변환, deref coercion이라고 해요), 함수를 이름으로 넘길 때는 그렇게 해 줄 수 없거든요.
// &str을 받아요: 문자열 리터럴, &String 등에서 모두 통해요fn shout(word: &str) -> String { word.to_uppercase()}
fn is_long(word: &str) -> bool { word.len() > 4}
fn main() { let words = vec!["crab", "rustacean", "ferris"];
// 아이템이 &str이라서 shout를 이름으로 넘길 수 있어요 let loud: Vec<String> = words.iter().copied().map(shout).collect(); println!("{:?}", loud);
// filter는 &&str을 줘요: 짧은 클로저로 감싸면 Rust가 대신 변환해 줘요 let long: Vec<&str> = words.iter().copied().filter(|w| is_long(w)).collect(); println!("{:?}", long);}실행 결과:
["CRAB", "RUSTACEAN", "FERRIS"]["rustacean", "ferris"]이 예제의 단어는 영어로 남겨 뒀어요. len()은 바이트 수를 세는데, 한글은 한 글자가 3바이트라서 결과가 달라지기 때문이에요. &str과 String의 차이는 문자열에서 더 볼 수 있어요.
자주 보게 되는 곳
섹션 제목: “자주 보게 되는 곳”타입을 붙인 정렬 매개변수. scores.sort_by(|a: i32, b: i32| b.cmp(&a))는 expected closure signature `for<'a, 'b> fn(&'a {integer}, &'b {integer}) -> _`와 함께 E0631을 내요. for<'a, 'b> 부분은 “어떤 대여든”이라는 뜻일 뿐이에요. 해결: |a, b| b.cmp(a). 정렬을 참고하세요.
소유 타입으로 쓴 맵 항목. HashMap<String, i32>에서 stock.iter().map(|(name, count): (String, i32)| ...)라고 쓰면 expected closure signature `fn((&String, &_)) -> _`가 나와요. 맵의 .iter()는 키와 값을 둘 다 빌려줘요. 해결: 타입 명시를 지우거나, (&String, &i32)라고 쓰세요.
내가 만든 제네릭 함수. fn apply<F: Fn(i32) -> i32>(f: F, x: i32)에 클로저 |x: f64| x / 2.0을 넘기면, rustc는 바운드를 가리키며 expected closure signature `fn(i32) -> _`와 found closure signature `fn(f64) -> _`를 알려 줘요. 해결: 클로저가 i32를 받게 하고 안에서 변환하거나(x as f64 / 2.0), 정말 f64를 원했다면 바운드를 바꾸세요. 클로저를 받는 함수 작성하기를 참고하세요.
String을 쓴 for_each. Vec<&str>에서 words.iter().for_each(|w: String| ...)라고 쓰면 fn(&&str)을 기대한다고 나와요. &str은 String이 아니에요. 해결: |w| 또는 |w: &&str|.
관련 에러
섹션 제목: “관련 에러”- E0593: closure takes the wrong number of arguments: 매개변수의 개수를 다루는 자매 에러예요(예: 튜플 하나가 넘어오는데
|a, b|라고 쓴 경우). - E0057: closure called with the wrong number of arguments: 내가 클로저를 직접 호출하면서 값을 너무 적게 또는 너무 많이 넘겼어요.
- E0308: mismatched types: 일반적인 “타입이 틀림” 에러예요. 예를 들어 클로저의 반환 값 타입이 틀렸거나,
fn(...)포인터 매개변수일 때 나와요. - E0599: no method found: E0631 뒤에 따라오는
collect나sum의 “trait bounds were not satisfied” 에러예요. E0631과 함께 사라져요. - E0507: cannot move out of a borrowed value:
.iter()로 빌리기만 한 아이템의 소유권을 가져가려고 할 때 나와요. - 전체 컴파일러 에러: 에러 페이지 전체 목록이에요.
이해도 점검
섹션 제목: “이해도 점검”이해도 점검
1. let v = vec![1, 2, 3];일 때 v.iter().map(|x| ...)의 클로저는 어떤 타입을 받을까요?
2. note에 expected closure signature fn(&{integer}) -> _, found closure signature fn(i32) -> _라고 나와요. 가장 빠른 해결책은 무엇일까요?
3. prices.iter().map(...) 안의 |&p| p * 2는 무슨 일을 할까요?
점수: 0 / 3
더 알아보기
섹션 제목: “더 알아보기”- E0631 — Rust error codes index (영어): 이 에러의 공식 설명이에요.
- 반복자로 일련의 아이템들 처리하기 — The Rust Programming Language 한국어판:
iter,into_iter,iter_mut이 각각 무엇을 넘겨주는지 다뤄요. - 클로저 — The Rust Programming Language 한국어판: 클로저 매개변수의 타입을 추론하는 방식과, 직접 써야 할 때를 설명해요.
Iterator::copied— 표준 라이브러리 문서 (영어):&T의 반복자를T의 반복자로 바꿔요.slice::sort_by— 표준 라이브러리 문서 (영어): 비교 클로저가 참조자 두 개를 받는다는 걸 시그니처로 보여 줘요.