Rust Iterator::map에서 Result가 실패하면 반복을 중단하고 에러 전파하기
1. 문제 정의
Rust에서 Iterator::map의 클로저가 Result<T, E>를 반환하는 시나리오는 매우 흔하다. 예를 들어 id 목록에서 각각을 조회하는 함수가 있다.
| |
이를 다음과 같이 실행하면 문제가 생긴다.
| |
parent_ids 중 단 하나라도 조회에 실패하면 unwrap()은 즉시 panic을 일으켜 프로세스가 중단된다. 반대로 flat_map으로 바꾸면 실패가 잘못 무시된다. 이 글에서 다루는 질문은 다음과 같다.
“Iterator::map이 Result::Err를 반환할 때, 반복을 멈추고 에러를 반환하는 방법은 무엇인가?” (출처: StackOverflow 26368288)
2. 표면 증상과 원인 후보
표면 증상
| 접근 방식 | 실패 시 동작 | 에러 정보 |
|---|---|---|
.map(...).unwrap() | 즉시 panic → 프로그램 크래시 | panic이지만 복구 불가능, 직접 처리 불가 |
.flat_map(...) | 에러 요소를 조용히 건너뜀 | 에러 정보 전부 유실 |
.collect::<Result<_,_>>() | 첫 Err에서 반복 중단 후 Err 반환 | 에러를 위로 전파 |
원인 후보와 배제 과정
unwrap()/expect(): 에러를 절대 무시하지 않지만,Err일 때 panic을 일으켜 제어 흐름이 끊긴다. “반복을 멈추고 에러를 반환"이라는 요구를 충족하지 못한다. → 배제flat_map(|id| find(id).into_iter()):Result::into_iter()는Item이Result이므로Ok(it)이면 1개 요소,Err이면 0개 요소를 낸다. 따라서 flat_map은 Err 요소를 무시(0개의 항목으로 취급)한다. 에러 정보가 유실되는 문제. → 배제 (핵심 함정)
3. 근본 원인 분석
핵심은 Result가 FromIterator를 구현했다는 점이다.
Rust의 collect()는 타입이 FromIterator<T>를 구현하면 그 형태로 수집할 수 있다. Result<V, E>는 FromIterator<Result<A, E>>를 구현하며, 동작 방식은 다음과 같다.
- 모든 요소가
Ok(a)이면Ok(iter.collect::<V>())를 반환 - 요소 중 하나라도
Err(e)이면 그 시점에 순회를 멈추고 해당Err(e)를 그대로 반환
즉 let items: Result<Vec<_>, _> = tasks.iter().map(find).collect(); 형태는 “첫 번째 실패에서 반복을 중단하고 에러를 위로 전파"라는 요구를 언어 차원에서 그대로 제공한다. 별도 루프나 상태 플래그가 필요하지 않다.
검증 환경 (재현 실험)
| |
| |
실행 결과: Err("not found: \"2\"") — "2"에서 반복이 멈추고 에러가 반환된다. "3"는 절대 처리되지 않았기 때문에 반복 중단이 입증된다.
4. 코드 해결책 (Best Answer)
가장 간단하고 권장되는 방법은 collect에 Result 타입 힌트를 주는 것이다.
| |
에러를 상위 함수로 그대로 전파하고 싶다면 ? 연산자와 함께 쓰는 것이 자연스럽다.
| |
속도와 호환성: collect는 각 Result의 FromIterator 구현을 통해 동작하므로 절대로 unwrap이나 flat_map의 오남용보다 더 안전하고, 핵심 실용 용례 대부분은 이를 표준 관용구로 채택한다.
해결책 비교
| 방식 | panic 유무 | 에러 상실 | 중단 시점 | 권장 |
|---|---|---|---|---|
unwrap() | panic하로 크래시 | 없음(대신 크래시) | 실패 지점 | ✗ |
flat_map | 없음 | 있음 (에러 무시) | 없음 (계속) | ✗ |
collect::<Result<Vec<_>,_>>() | 없음 | 없음 | 첫 Err | ✓ |
collect + ? | 없음 | 없음 | 첫 Err, 상위 전파 | ✓ (전파 시) |
실패한 해결책 (초보 구현의 흔한 오류)
아래는 실무에서 흔히 보이지만 모두 원하는 동작을 만족하지 못하는 접근이다.
| |
unwrap은 테스트 환경에서만 허용되고, flat_map은 에러라는 운영 정보를 ‘성공만 남기기’ 때문에 두 방식 모두 실무 코드에서 사용하면 안 된다. 둘 다 “에러를 위로 전파"하라는 요구를 어긴다.
5. 향후 예방 조치 / 운영 예방책 / DevTrace 독자 결론
- DevTrace 독자 결론:
Result요소를 순회할 때는unwrap도flat_map도 아니고,collect::<Result<Vec<_>,_>>()를 기본으로 선택한다. 이걸로 첫 실패에서 안전하게 중단하고 에러를 명시적으로 처리하며,?만 붙이면 상위 함수로 전파까지 된다. - 체크리스트
map클로저가Result를 반환하면 타입 어노테이션을Result<Vec<_>, _>로 선언.- 실패를 크래시로 만들지 말고
collect로 열거한다. - 함수 경계에서는
?로 전파, 최종 경계에서match로 에러 응답. flat_map을 쓰고 있다면 실제로 에러가 ‘조용히’ 버려지고 있는지 점검.