Day 11-13: Enums & Pattern Matching 정리

Day 11-13: Enums & Pattern Matching 정리

핵심 한줄: Rust enum은 "여러 가능성 중 하나"를 타입으로 표현하고, match는 모든 경우를 빠짐없이 처리하도록 강제한다.


🔑 핵심 개념

1. Enum — 여러 가능성을 하나의 타입으로

Python에서는 상태를 문자열이나 상수로 표현하지만 오타 위험이 있다. Rust enum은 가능한 값을 컴파일 타임에 확정한다.

enum TrafficLight {
    Red,
    Yellow,
    Green,
}
 
let light = TrafficLight::Red;  // :: 로 접근 (타입::variant)

2. Enum Variant의 3가지 형태

struct의 3종류와 대응:

enum Command {
    Quit,                          // Unit — 데이터 없음
    Echo(String),                  // Tuple — 이름 없는 데이터
    Move { x: i32, y: i32 },      // Struct — 이름 있는 데이터
}

3. 데이터를 담는 Enum — Python/TS에 없는 핵심 기능

enum Shape {
    Circle(f64),
    Rectangle(f64, f64),
}
  • 각 variant가 다른 형태의 데이터를 가질 수 있다
  • 하나의 타입으로 Vec<Shape>에 이종 데이터를 담을 수 있다
  • Python class 상속이나 TS union type보다 간결하고 안전

🔍 Pattern Matching

match — 모든 경우를 처리해야 함 (exhaustiveness)

match light {
    TrafficLight::Red => println!("멈춤"),
    TrafficLight::Yellow => println!("주의"),
    TrafficLight::Green => println!("출발"),
    // variant를 빠뜨리면 컴파일 에러!
}

variant를 추가하면 처리 안 한 곳을 컴파일러가 전부 찾아준다. 대규모 코드베이스에서 상태 추가 시 실수를 원천 차단.

match에서 데이터 꺼내기 (destructuring)

match shape {
    Shape::Circle(radius) => println!("반지름: {}", radius),
    Shape::Rectangle(w, h) => println!("{}x{}", w, h),
}

와일드카드, 범위, OR 매칭

match score {
    90..=100 => "A",         // 범위 (양쪽 포함)
    0 | 1 | 2 => "거의 포기", // OR: 여러 값
    _ => "F",                // 나머지 전부
}

Guard 조건

match cmd {
    Command::Wait(sec) if *sec < 5 => println!("{}초 대기", sec),
    Command::Wait(_) => println!("너무 오래 기다림"),
}

같은 variant를 여러 arm으로 나눠서 각각 다른 조건을 붙일 수 있다.

Tuple & Struct 분해

// tuple
match (x, y) {
    (0, 0) => println!("원점"),
    (x, 0) => println!("x축 위: {}", x),
    (x, y) => println!("({}, {})", x, y),
}
 
// struct
match point {
    Point { x: 0, y } => println!("y축 위: y={}", y),
    Point { x, y } => println!("({}, {})", x, y),
}

📦 가장 많이 쓰는 Enum 2가지

Option<T> — "값이 있거나 없거나" (Rust의 null 대체)

// 표준 라이브러리에 정의
enum Option<T> {
    Some(T),  // 값이 있다
    None,     // 값이 없다
}

왜 null보다 나은가:

  • None일 수 있는 값은 반드시 처리해야 컴파일됨
  • Python처럼 None 체크를 깜빡할 수 없음

값 꺼내는 3가지 방법:

// 1. match — 가장 명시적
match result {
    Some(v) => println!("{}", v),
    None => println!("없음"),
}
 
 
// 2. if let — 한 가지 경우만 관심 있을 때
if let Some(v) = result {
    println!("{}", v);
}
 
// 3. unwrap_or — 기본값 제공
let v = result.unwrap_or(0.0);

⚠️ unwrap() 주의:

result.unwrap()  // None이면 panic! (프로그램 즉시 종료)

Python의 "일단 돌려보자" 습관으로 남발하면 Rust의 안전성을 스스로 버리는 것.

Result<T, E> — "성공이거나 실패거나"

// 표준 라이브러리에 정의
enum Result<T, E> {
    Ok(T),   // 성공 + 결과값
    Err(E),  // 실패 + 에러 정보
}
let good: Result<f64, _> = "42.5".parse::<f64>();  // Ok(42.5)
let bad: Result<f64, _> = "hello".parse::<f64>();   // Err(...)
 
match good {
    Ok(n) => println!("숫자: {}", n),
    Err(e) => println!("에러: {}", e),
}

Option vs Result 비교

OptionResult<T, E>
경우 1Some(T) — 값 있음Ok(T) — 성공
경우 2None — 값 없음Err(E) — 실패 + 이유
용도"없을 수 있다""실패할 수 있고, 이유가 중요"
예시리스트 검색파일 읽기, 파싱

둘 다 "이것 아니면 저것" 이라는 동일한 이진 구조. 차이는 실패 쪽에 정보가 있느냐 없느냐뿐.

상호 변환 가능:

  • OptionResult: .ok_or("에러 메시지")
  • ResultOption: .ok()

🔧 문자열 파싱 패턴

let input = "add Kim 95.5";
 
// 1. 공백 분리
let parts: Vec<&str> = input.split_whitespace().collect();
// ["add", "Kim", "95.5"]
 
// 2. &str → String 변환
let name: String = parts[1].to_string();
 
// 3. &str → 숫자 변환 (Result 반환)
let score: Result<f64, _> = parts[2].parse::<f64>();
 
// 4. 슬라이스 패턴 매칭
match parts.as_slice() {
    ["add", name, score] => println!("추가: {} {}", name, score),
    ["remove", name] => println!("제거: {}", name),
    _ => println!("모름"),
}

Python 대응:

PythonRust설명
input.split()input.split_whitespace()공백 분리
parts[1]parts[1]인덱스 접근
그냥 사용parts[1].to_string()소유권 있는 String 변환
float("95.5")"95.5".parse::<f64>()숫자 변환 (Result 반환)

🔗 Python/TS 비교 종합

Python/TSRust차이
None 체크 깜빡 가능Option<T> — 처리 강제컴파일 타임 안전성
try/exceptResult<T, E>에러가 타입에 인코딩
if/elif/elsematch — exhaustiveness빠뜨리면 컴파일 에러
TS union type (데이터 못 담음)enum (데이터 포함)훨씬 강력
Python Enum (이름만)Rust enum (ADT)데이터 포함 가능
Python match (3.10+)Rust matchexhaustiveness 보장

🔗 참고 자료