❓
Option<T> — nil이 사라진 세계에서 살아남기
Ruby의 nil과 &. 연산자가 Rust에서는 어떻게 바뀌는가
Ruby에서 nil은 공기 같은 존재다. 배열에서 없는 인덱스를 참조하면 nil, Hash에서 없는 키를 참조하면 nil, 메서드가 명시적으로 반환하지 않으면 nil. 그리고 nil에 메서드를 호출하면 NoMethodError. 프로덕션 에러의 상당수가 여기서 나온다.
Rust는 nil을 아예 없앴다. 대신 Option<T>이라는 enum을 쓴다.
enum Option<T> {
Some(T), // 값이 있음
None, // 값이 없음
}
Ruby와 비교
# Ruby
def find_user(id)
users[id] # nil일 수 있음
end
user = find_user(99)
user.name # NoMethodError if nil!
// Rust
fn find_user(id: u32) -> Option<User> {
// Some(user) 또는 None
}
let user = find_user(99);
// user.name // 컴파일 에러! Option<User>에 name 메서드 없음
Rust는 Option<User>와 User를 다른 타입으로 취급한다. Option에서 값을 꺼내야만 쓸 수 있다.
값 꺼내기 — unwrap (위험)
let user = find_user(99).unwrap(); // None이면 panic!
Ruby에서 nil에 !를 붙이는 것과 비슷한 위험도. 프로덕션 코드에서는 피해야 한다.
안전하게 꺼내기 — match, if let, map
// match
match find_user(99) {
Some(user) => println!("{}", user.name),
None => println!("사용자 없음"),
}
// if let (하나만 체크)
if let Some(user) = find_user(99) {
println!("{}", user.name);
}
// map — Ruby의 &.에 가장 가까움
let name = find_user(99).map(|u| u.name);
// Option<String> 반환
&. → map/and_then 매핑
Ruby의 safe navigation operator와 Option 메서드의 대응:
# Ruby
user&.name # nil이면 nil 반환
user&.address&.city # 체이닝
user&.name || "unknown" # 기본값
// Rust
user.map(|u| u.name) // None이면 None
user.and_then(|u| u.address).map(|a| a.city) // 체이닝
user.map(|u| u.name).unwrap_or("unknown") // 기본값
map은 Some 안의 값을 변환한다. and_then은 변환 결과가 또 Option일 때 사용(flatMap). unwrap_or은 None일 때 기본값.
unwrap_or_else — 기본값이 비싼 경우
let name = find_user(99)
.map(|u| u.name)
.unwrap_or_else(|| fetch_default_name()); // None일 때만 실행
Ruby의 user&.name || expensive_default에서 ||는 항상 우항을 평가하지 않는다. Rust의 unwrap_or_else도 같은 lazy 평가.
핵심 포인트
1
Option<T>는 Some(값) 또는 None — nil의 타입 안전한 대체
2
unwrap은 위험 — match, if let, map으로 안전하게 처리
3
&. → map, 체이닝 → and_then, 기본값 → unwrap_or
4
Option<User>와 User는 다른 타입 — 반드시 꺼내서 사용
장점
- ✓ NoMethodError on nil이 아예 불가능하다 — 컴파일 타임에 잡힘
- ✓ None 처리를 빠뜨리면 컴파일이 안 된다
단점
- ✗ Ruby처럼 nil을 무시하고 넘어갈 수 없어서 초반에 번거롭다
- ✗ Option 체이닝이 길어지면 가독성이 떨어질 수 있다
사용 사례
DB 조회 결과가 없을 수 있을 때 — find vs find_by
설정값이 있을 수도 없을 수도 있을 때