🦀
에러 처리
에러 처리 · 선수: 타입 1~4강
15. Result<T,E> 와 Option<T> 다루기
Rust 의 에러 처리는 두 가지 enum 으로 압축됩니다 — Option<T> 는 "값이 없을 수 있다", Result<T,E> 는 "실패할 수 있다". try/catch 가 없는 대신 타입으로 모든 실패를 표현하기 때문에 호출자가 처리하지 않으면 컴파일러가 잡아냅니다. 이 강의에서 두 enum 의 변환·헬퍼 메서드를 한 번에 정리합니다.
RustResultOption에러 처리match
소요 시간
⏱ 약 1.5시간
난이도
📊 중급
선수 조건
🎯 타입 1~4강
결과물
Rust 의 에러 처리는 두 가지 enum 으로 압축됩니다 — Option<T> 는 "값이 없을 수 있다", Result<T,E> 는 "실패할 수 있다". try/catch 가 없는 대신 타입으로 모든 실패를 표현하기 때문에 호출자가 처리하지 않으면 컴파일러가 잡아냅니다. 이 강의에서 두 enum 의 변환·헬퍼 메서드를 한 번에 정리합니다.
이 강의에서 배우는 것
- 1Result<T,E> 와 Option<T> 의 차이를 안다
- 2match 와 헬퍼 메서드로 분기 처리한다
- 3.ok() / .ok_or() 로 두 타입을 변환한다
- 4함수 시그니처에 Result 반환을 명시한다
- 5panic 과 Result 의 사용 기준을 안다
소개
다른 언어는 예외(exception) 가 함수 시그니처에 보이지 않거나(unchecked) 따로 throws 절을 적습니다. Rust 는 반환 타입에 Result/Option 으로 명시 — 호출자가 어떻게 실패하는지 한눈에 보고, 처리 누락은 컴파일 에러가 됩니다.
핵심 개념
1) Result<T, E>
rust
enum Result<T, E> {
Ok(T),
Err(E),
}성공값 타입 T 와 실패값 타입 E 를 모두 시그니처에 명시. 둘 다 데이터를 가집니다.
2) Option<T> vs Result<T,E>
| 관점 | Option | Result |
|---|---|---|
| 용도 | 값이 있을 수도 / 없을 수도 | 성공 / 실패 + 실패 정보 |
| 없을 때 | None (정보 없음) | Err(E) (실패 이유 포함) |
| 언제 쓰나 | 리스트에서 찾기 실패 등 정보 없는 부재 | 파일 IO·네트워크·파싱 등 실패 이유 중요 |
3) 자주 쓰는 헬퍼
| 메서드 | 동작 |
|---|---|
| .unwrap() | Ok/Some 이면 값, 아니면 panic |
| .expect("msg") | panic 시 메시지 명시 |
| .unwrap_or(d) | 실패 시 기본값 |
| .unwrap_or_else(|e| ...) | 실패 시 컴퓨트한 기본값 |
| .ok() | Result → Option (Err 정보 버림) |
| .map(|x| ...) | Ok/Some 일 때만 변환 |
| .map_err(|e| ...) | Err 만 변환 |
핵심 예제
문자열을 정수로 — Result 반환:
rust
fn parse_age(s: &str) -> Result<u32, String> {
match s.trim().parse::<u32>() {
Ok(n) if n > 150 => Err(format!("비현실적 나이: {}", n)),
Ok(n) => Ok(n),
Err(e) => Err(format!("파싱 실패: {}", e)),
}
}
fn main() {
for s in ["30", "abc", "999"] {
match parse_age(s) {
Ok(n) => println!("{} OK -> {}", s, n),
Err(e) => println!("{} ERR -> {}", s, e),
}
}
}Option ↔ Result 변환:
rust
fn find(v: &[i32], target: i32) -> Option<usize> {
v.iter().position(|&x| x == target)
}
fn find_or_err(v: &[i32], target: i32) -> Result<usize, String> {
find(v, target).ok_or(format!("{} not found", target))
}
fn main() {
let v = [10, 20, 30];
println!("{:?}", find_or_err(&v, 20)); // Ok(1)
println!("{:?}", find_or_err(&v, 99)); // Err("99 not found")
}헬퍼 메서드 체이닝:
rust
fn main() {
let s = " 42 ";
let n = s.trim().parse::<i32>()
.map(|x| x * 2)
.unwrap_or(-1);
println!("{}", n); // 84
}자주 하는 실수
Q. 그럼 매번 match 를 다 써야 하나요?
A. 아닙니다. 다음 강의의 `?` 연산자가 "실패면 즉시 반환" 을 한 글자로 줄여줍니다. .map / .and_then 으로 체이닝하는 패턴도 흔히 씁니다.
Q. .unwrap() 을 쓰면 안 되나요?
A. 프로토타이핑·예제·테스트엔 OK. 실무 코드는 **"이건 절대 None/Err 일 수 없다" 가 코드 구조로 명백할 때만** 쓰는 게 안전. 사용자 입력·외부 IO 결과엔 절대 .unwrap() 금지.
Q. Option 과 Result 중 뭘 쓰나요?
A. **실패 이유가 의미 있으면 Result, 단순히 부재면 Option**. 예: 리스트 검색은 Option, 파일 열기는 Result.
정리
- Option = 부재 표현, Result = 실패 이유까지 표현
- 실패가 가능한 함수는 반환 타입에 명시 → 처리 누락이 컴파일 에러
- match / .unwrap_or / .map / .ok_or 등 다양한 분기 방법
- .unwrap() 은 신중히
과제
- fn divide(a: i32, b: i32) -> Result<i32, String> 작성 (b=0 이면 Err)
- 사용자가 입력한 문자열을 받아 u32 로 파싱하고, 1~120 범위면 Ok 아니면 Err 반환하는 함수
- Vec<Result<i32, String>> 에서 Ok 값만 합치는 함수 (.filter_map / .iter 활용)