← Rust 강의 목록으로
🦀
타입 시스템
타입 시스템 · 선수: 11강

12. 열거형 + Option<T> 입문

Rust 의 enum 은 C 의 enum 과 차원이 다릅니다 — 각 variant 가 **다른 타입의 데이터를 가질 수 있는** sum type 입니다. 그리고 가장 자주 쓰이는 enum 인 Option<T> (Some(T) 또는 None) 가 어떻게 null pointer 의 1조 달러 실수를 풀어내는지 살펴봅니다.

RustenumOptionsum typenull 안전
소요 시간
약 1.5시간
난이도
📊 중급
선수 조건
🎯 11강
결과물
Rust 의 enum 은 C 의 enum 과 차원이 다릅니다 — 각 variant 가 **다른 타입의 데이터를 가질 수 있는** sum type 입니다. 그리고 가장 자주 쓰이는 enum 인 Option<T> (Some(T) 또는 None) 가 어떻게 null pointer 의 1조 달러 실수를 풀어내는지 살펴봅니다.

이 강의에서 배우는 것

  • 1enum 각 variant 에 서로 다른 데이터를 담는다
  • 2Option<T> 로 "값이 없을 수도 있음" 을 타입으로 표현한다
  • 3match / if let 으로 enum 을 분해한다
  • 4Option 의 .unwrap() / .unwrap_or() / .map() 사용법을 안다
  • 5null 이 없는 언어가 어떻게 안전성을 끌어올리는지 설명한다

소개

Tony Hoare 가 1965년 null pointer 를 발명한 것을 "1조 달러짜리 실수" 라 회고했습니다. Rust 는 null 이라는 개념을 언어 차원에서 없애고 `Option<T>` 로 대체했습니다 — 값이 없을 수도 있다는 것이 **타입에 명시**되어 컴파일러가 처리 누락을 잡아냅니다.

핵심 개념

1) Rust 의 enum 은 sum type

rust
enum Message {
    Quit,                          // 데이터 없음
    Move { x: i32, y: i32 },       // named struct 형태
    Write(String),                  // tuple 형태 1개
    ChangeColor(i32, i32, i32),     // tuple 형태 3개
}

각 variant 가 사실상 서로 다른 타입의 "한 가지 경우". C 의 enum 처럼 정수 별칭이 아닙니다.

2) Option<T> — 빌트인

rust
enum Option<T> {
    None,
    Some(T),
}

표준 라이브러리에 포함돼 있고 어디서나 바로 쓸 수 있습니다 (use std::option::Option 불필요).

3) 분해 — match / if let

  • **match** — 모든 variant 처리 강제. 누락 시 컴파일 에러
  • **if let** — 한 variant 만 관심 있을 때 짧게

4) Option 헬퍼 메서드

메서드의미
.unwrap()Some 이면 안의 값, None 이면 panic
.unwrap_or(default)Some 이면 값, None 이면 default
.map(|x| ...)Some 인 경우만 변환
.and_then(|x| ...)Some 인 경우만 변환 (반환도 Option)
.is_some() / .is_none()boolean 검사

핵심 예제

match 로 모든 variant 처리:

rust
enum Coin { Penny, Nickel, Dime, Quarter }

fn value(c: Coin) -> u32 {
    match c {
        Coin::Penny   => 1,
        Coin::Nickel  => 5,
        Coin::Dime    => 10,
        Coin::Quarter => 25,
    }
}

fn main() { println!("{}", value(Coin::Quarter)); }

Option<T> 다루기:

rust
fn find_first_even(v: &[i32]) -> Option<i32> {
    for &x in v {
        if x % 2 == 0 { return Some(x); }
    }
    None
}

fn main() {
    let v = [1, 3, 4, 7, 8];
    match find_first_even(&v) {
        Some(x) => println!("found {}", x),
        None    => println!("not found"),
    }
    // if let — 한쪽만 관심
    if let Some(x) = find_first_even(&v) { println!("got {}", x); }
    // helper 메서드
    let v2: [i32; 0] = [];
    let result = find_first_even(&v2).unwrap_or(-1);
    println!("{}", result); // -1
}

data-carrying variant:

rust
enum Shape {
    Circle(f64),
    Rectangle { w: f64, h: f64 },
}

fn area(s: Shape) -> f64 {
    match s {
        Shape::Circle(r)            => std::f64::consts::PI * r * r,
        Shape::Rectangle { w, h }   => w * h,
    }
}

fn main() {
    println!("{}", area(Shape::Circle(2.0)));
    println!("{}", area(Shape::Rectangle { w: 3.0, h: 4.0 }));
}

자주 하는 실수

Q. .unwrap() 을 그냥 써도 되나요?

A. 프로토타이핑·테스트엔 OK. 실무 코드에선 "None 일 수 있다" 는 상황에서 panic 위험이 커지므로 .unwrap_or(...) / .map / ? 연산자(다음 강의들) 가 더 안전합니다.

Q. match 의 모든 variant 를 쓰기 귀찮은데 와일드카드는?

A. `_ =>` 로 나머지 모두 매칭. 하지만 가능하면 명시적으로 쓰는 게 좋습니다 — 나중에 variant 추가될 때 컴파일러가 알려주는 안전망이 사라지니까요.

Q. null 대신 Option 을 쓰는 게 그렇게 큰 차이인가요?

A. Java/Python 에서는 메서드를 호출하다가 NullPointerException 이 런타임에 터지지만, Rust 는 `T` 자리에 `None` 이 들어갈 수 없습니다. "이 함수는 실패할 수 있다" 가 타입에서 강제되니 처리 누락이 컴파일 에러.

정리

  • enum 각 variant 가 서로 다른 데이터를 가질 수 있음 (sum type)
  • Option<T> 가 null 을 대체 — 부재를 타입에 표현
  • match / if let 으로 분해, match 는 모든 variant 처리 강제
  • Option 의 .unwrap / .unwrap_or / .map 헬퍼 사용

과제

  1. fn find(v: &[i32], target: i32) -> Option<usize> 작성
  2. enum Event { Login(String), Logout, Click { x: i32, y: i32 } } 의 각 variant 를 match 로 다른 메시지 출력
  3. Option<i32> 두 개를 더하는 함수 — 둘 다 Some 이면 합, 하나라도 None 이면 None 반환
예제 코드 / 강의 자료

전체 강의 자료와 예제 코드는 GitHub에서 자유롭게 받아볼 수 있습니다.

GitHub에서 보기 ↗