콘텐츠로 이동

Rust Option

Rust에는 null이 없어요. 값이 없을 수도 있다면, 그 사실을 Option을 써서 타입에 직접 드러내요. 처음에는 괜히 번거로운 절차처럼 느껴질 수 있지만, 덕분에 버그 한 종류가 통째로 사라져요. 다른 언어에서 셀 수 없이 많은 프로그램을 죽게 만든 “null 참조” 말이에요.

Option은 배리언트가 두 개인 열거형이에요.

enum Option<T> {
Some(T), // 값이 있어요
None, // 값이 없어요
}

사실 이미 본 적이 있어요. 벡터의 .get()과 HashMap의 .get()이 둘 다 Option을 반환하거든요.

fn main() {
let has_value: Option<i32> = Some(5);
let no_value: Option<i32> = None;
println!("{:?}", has_value);
println!("{:?}", no_value);
}

실행 결과:

Some(5)
None
▶ 직접 실행해 보기

값을 안전하게 꺼내는 방법은 match예요. Rust는 두 경우를 모두 처리하도록 강제하기 때문에, “값이 없는” 경우를 실수로 빠뜨릴 수가 없어요.

fn main() {
let age: Option<i32> = Some(30);
match age {
Some(value) => println!("나이: {value}살"),
None => println!("나이가 입력되지 않았어요"),
}
}
▶ 직접 실행해 보기
fn find_user(id: u32) -> Option<&'static str> {
if id == 1 {
Some("Alice")
} else {
None
}
}
fn main() {
match find_user(1) {
Some(name) => println!("찾았어요: {name}"),
None => println!("사용자를 찾을 수 없어요"),
}
match find_user(99) {
Some(name) => println!("찾았어요: {name}"),
None => println!("사용자를 찾을 수 없어요"),
}
}

실행 결과:

찾았어요: Alice
사용자를 찾을 수 없어요
▶ 직접 실행해 보기

사용자를 찾지 못했을 때 null을 반환하거나 예외를 던지는 대신, 함수의 반환 타입 자체가 모든 호출자에게 “아무것도 못 찾을 수도 있어요”라고 알려 줘요. 그리고 컴파일러는 호출자가 그 가능성을 무시하지 못하게 해요.

간단한 경우라면 match를 통째로 쓰는 게 과하게 느껴질 수 있어요. 그럴 때 쓸 수 있는 지름길이 몇 가지 있어요.

fn main() {
let age: Option<i32> = None;
let value = age.unwrap_or(0); // 값이 없으면 0을 대신 써요
println!("{value}");
}
▶ 직접 실행해 보기

.unwrap()도 있어요. 값을 바로 꺼내 주지만, None이면 패닉(panic)이 일어나서 프로그램이 멈춰요.

let age: Option<i32> = Some(30);
let value = age.unwrap(); // age가 Some이니까 여기서는 괜찮아요

슬라이스에서 첫 번째 짝수를 반환하고, 짝수가 없으면 None을 반환하는 fn first_even(numbers: &[i32]) -> Option<i32>를 작성해 보세요. [3, 7, 8, 10]의 결과는 match로, [1, 3, 5]의 결과는 .unwrap_or(0)으로 출력하세요. 힌트: 슬라이스를 돌다가 n % 2 == 0이 되는 순간 return Some(n)을 하고, 반복문이 끝난 뒤에는 None을 반환하세요.

정답 보기
fn first_even(numbers: &[i32]) -> Option<i32> {
for &n in numbers {
if n % 2 == 0 {
return Some(n);
}
}
None
}
fn main() {
match first_even(&[3, 7, 8, 10]) {
Some(n) => println!("첫 번째 짝수: {n}"),
None => println!("짝수가 없어요"),
}
let fallback = first_even(&[1, 3, 5]).unwrap_or(0);
println!("기본값 적용: {fallback}");
}

실행 결과:

첫 번째 짝수: 8
기본값 적용: 0
▶ 직접 실행해 보기

"volume"이 80으로 설정된 항목 하나만 들어 있는 설정 HashMap을 만드세요. 그런 다음 키 "volume"과 "brightness"를 돌면서, 값이 있으면 그 값을, 없으면 “설정되지 않았다”는 메시지를 출력하세요. 힌트: HashMap의 .get(key)는 Option을 반환하니까, 바로 match할 수 있어요.

정답 보기
use std::collections::HashMap;
fn main() {
let mut settings = HashMap::new();
settings.insert("volume", 80);
for key in ["volume", "brightness"] {
match settings.get(key) {
Some(value) => println!("{key} = {value}"),
None => println!("{key}: 설정되지 않았어요"),
}
}
}

실행 결과:

volume = 80
brightness: 설정되지 않았어요
▶ 직접 실행해 보기
  • Option<T>는 있을 수도 있고 없을 수도 있는 값을 나타내요. Rust에는 null이 없어요.
  • Some(value)는 값을 담고 있고, None은 아무것도 없다는 뜻이에요.
  • match는 두 경우를 모두 안전하게 처리하는 방법이에요. 컴파일러는 한쪽을 빠뜨리지 못하게 해요.
  • .unwrap_or(default)는 기본값을 대신 줘요. .unwrap()은 값이 None이면 패닉을 일으키니까 아껴서 쓰세요.

이해도 점검

1. 값이 없을 수도 있을 때, Rust는 null 대신 무엇을 쓰나요?

2. None 값에 .unwrap()을 호출하면 어떻게 되나요?

3. age가 None일 때 age.unwrap_or(0)은 어떻게 동작하나요?

4. match가 Option에서 값을 꺼내는 안전한 방법으로 여겨지는 이유는 무엇인가요?

5. 이 페이지에서 언급한 메서드 중 이미 Option을 반환하는 것은 무엇인가요?

점수: 0 / 5