
안녕하세요
오늘은 Rust의 unwrap 에 대해 알아보도록 하겠습니다
서론
최근 회사에서 Rust로 작성된 프로젝트를 분석할 일이 있었습니다.
코드를 보다 보니 unwrap()이 생각보다 많이 사용되고 있었습니다.
let user = find_user(id).unwrap();
unwrap()을 이렇게 자주 사용해도 괜찮을지 궁금증이 생겼습니다.
이번 글에서는 unwrap()의 동작 방식과 특징,
그리고 사용할 때 주의해야할 점을 정리해보겠습니다.
동작
unwrap()의 동작은 단순합니다
let x = value.unwrap();
- Some(value) → value 반환
- None → 프로그램 종료 (panic!)
Rust의 Option<T>는 이렇게 정의됩니다
pub enum Option<T> {
Some(T),
None,
}
Rust의 unwrap()의 구현은 다음과 같습니다
// result.rs
#[inline(always)]
#[track_caller]
#[stable(feature = "rust1", since = "1.0.0")]
pub fn unwrap(self) -> T
where
E: fmt::Debug,
{
match self {
Ok(t) => t,
Err(e) => unwrap_failed("called `Result::unwrap()` on an `Err` value", &e),
}
}
실패 가능성을 타입에 표현한 것이 Option<T> 입니다
하지만 unwrap()은 성공했다고 가정하는 메서드 입니다
→ 컴파일 타임에 강제되던 실패 처리를 런타임 체크로 넘기는 방식
편의성 및 주의 사항
unwrap()은 코드를 매우 간단하게 만들어 줍니다
match를 쓰면 이렇게 쓰는 것을
match value {
Some(v) => println!("{}", v),
None => println!("값이 없습니다."),
}
unwrap을 쓰면 이렇게 쓸 수 있습니다 (대신 런타임 에러는 날 수 있습니다)
println!("{}", value.unwrap());
프로젝트 초반에 개발 편의성을 높이기 위해 unwrap()을 자주 사용합니다
그렇지만 unwrap()은 실패를 처리하지 않고, 프로그램을 종료시킵니다
다음과 같은 코드가 있을 때,
let user = find_user(id).unwrap();
만약 해당 id의 사용자가 없다면
→ 프로그램이 그대로 종료됩니다
사용자 한 명의 요청 때문에 프로그램이 종료됩니다
compare with ‘?’
Rust에 에러를 처리하는 방법 중에 ?을 쓰는 방법도 있습니다
unwrap
let x = foo().unwrap();
- 에러가 나면 panic!
?
let x = foo()?;
- 에러를 호출자에게 전달
- 상위에서 처리 가능
Rust에서는 일반적으로 ?를 사용하는 방식이 더 권장됩니다
unwrap_or
unwrap()이 값이 없을 경우 프로그램을 종료시킨다면,
unwrap_or()는 조금 더 부드러운 선택을 제공합니다
let value: Option<i32> = None;
let x = value.unwrap_or(0);
println!("{}", x); // 0
- Some(v) → v 반환
- None → 지정한 기본값 반환
unwrap_or() vs unwrap_or_else()
하나 더 알아두면 유용한 메서드가 있습니다
unwrap_or(default_value)
unwrap_or_else(|| compute_default())
- 차이점은 기본값이 언제 계산되는가 입니다
unwrap_or()은 get_cost()가 항상 실행됩니다
- Some 이어도 실행됩니다
fn main() {
let r: Option<i32> = Some(42);
let x = r.unwrap_or(get_cost(3));
println!("main: x is {}", x);
println!();
let s: Option<i32> = None;
let y = s.unwrap_or(get_cost(10));
println!("main: y is {}", y);
}
fn get_cost(x: i32) -> i32 {
println!("get_cost: x is {}", x);
x + 1
}
get_cost: x is 3
main: x is 42
get_cost: x is 10
main: y is 11
unwrap_or_else()는 None 일 때만 실행됩니다
fn main() {
let r: Option<i32> = Some(42);
let x = r.unwrap_or_else(|| get_cost(3));
println!("main: x is {}", x);
println!();
let s: Option<i32> = None;
let y = s.unwrap_or_else(|| get_cost(10));
println!("main: y is {}", y);
}
fn get_cost(x: i32) -> i32 {
println!("get_cost: x is {}", x);
x + 1
}
main: x is 42
get_cost: x is 10
main: y is 11
기본값 계산 비용이 크다면 unwrap_or_else()를 사용하는 것이 좋습니다
expect
expect()는 unwrap()과 거의 같지만,
에러 메시지를 직접 작성할 수 있습니다
r.expect("이 값은 반드시 존재해야 한다");
thread 'main' (15464) panicked at src\\main.rs:3:7:
이 값은 반드시 존재해야 한다: "error" 📌
stack backtrace:
0: std::panicking::panic_handler
프로그램이 종료되더라도, 왜 종료되었는지 더 명확하게 알 수 있습니다
결론
unwrap()은 나쁜 메서드는 아닙니다
다만 ‘실패를 어떻게 다룰 것인가’에 대한 선택입니다
테스트 코드나 명확한 값이 보장되는 경우에는 유용하지만,
외부 입력을 다루는 코드에서는 신중하게 사용해야 합니다
다음 질문에 명확히 답할 수 있다면, unwrap()은 좋은 도구가 될 수 있습니다
이 실패는 정말 프로그램을 종료해야 하는 상황일까?
감사합니다.
'Rust' 카테고리의 다른 글
| [Rust] 패턴 바인딩 (Pattern Binding) (0) | 2026.02.17 |
|---|---|
| [Rust] Generic Lifetime (0) | 2026.02.16 |
| [Rust] 동시성 (0) | 2025.04.27 |
| [Rust] 컬렉션 (Collection) (0) | 2025.04.22 |
| [Rust] 에러 처리 (0) | 2025.03.30 |