Rust 열거형
열거형(enum, enumeration의 줄임말)은 정해진 몇 가지 가능성 중 하나가 될 수 있는 타입을 정의해요. 구조체가 “이것은 이 필드들을 모두 가지고 있다”를 표현한다면, 열거형은 “이것은 이 선택지들 중 정확히 하나다”를 표현해요.
열거형 정의하고 사용하기
섹션 제목: “열거형 정의하고 사용하기”enum Direction { North, South, East, West,}
fn main() { let heading = Direction::North;
match heading { Direction::North => println!("북쪽으로 가는 중"), Direction::South => println!("남쪽으로 가는 중"), Direction::East => println!("동쪽으로 가는 중"), Direction::West => println!("서쪽으로 가는 중"), }}실행 결과:
북쪽으로 가는 중여기서 match에 나머지를 모두 받는 _가 필요 없다는 점을 보세요. 가능한 Direction을 전부 나열했기 때문에, Rust는 이 match가 빠짐없이 완전하다는 걸 이미 알 수 있어요.
열거형은 데이터를 담을 수 있어요
섹션 제목: “열거형은 데이터를 담을 수 있어요”바로 여기서 Rust의 열거형은 다른 언어의 일반적인 열거형보다 한 걸음 더 나아가요. 각 배리언트(variant)가 자기만의 데이터를 담을 수 있거든요.
enum Shape { Circle(f64), // 반지름 Rectangle(f64, f64), // 너비, 높이}
fn area(shape: &Shape) -> f64 { match shape { Shape::Circle(radius) => 3.14159 * radius * radius, Shape::Rectangle(width, height) => width * height, }}
fn main() { let circle = Shape::Circle(3.0); let rectangle = Shape::Rectangle(4.0, 5.0);
println!("{:.2}", area(&circle)); println!("{:.2}", area(&rectangle));}실행 결과:
28.2720.00Shape::Circle은 f64 하나(반지름)를 담고, Shape::Rectangle은 두 개를 담아요. match는 각 갈래(arm)에서 그 데이터를 바로 꺼내 줘요. 따로 “꺼내는” 단계가 필요 없어요.
열거형도 메서드를 가질 수 있어요
섹션 제목: “열거형도 메서드를 가질 수 있어요”구조체처럼 열거형도 impl 블록에 메서드를 가질 수 있어요. 메서드 안에서는 보통 match self로 각 배리언트마다 무엇을 할지 정해요.
#[derive(Debug)]enum Light { Red, Yellow, Green,}
impl Light { fn seconds(&self) -> u32 { match self { Light::Red => 30, Light::Yellow => 3, Light::Green => 25, } }
fn next(&self) -> Light { match self { Light::Red => Light::Green, Light::Green => Light::Yellow, Light::Yellow => Light::Red, } }}
fn main() { let mut light = Light::Red;
for _ in 0..4 { println!("{:?} 신호: {}초", light, light.seconds()); light = light.next(); }}실행 결과:
Red 신호: 30초Green 신호: 25초Yellow 신호: 3초Red 신호: 30초next는 완전히 새로운 Light를 돌려주고, light = light.next();가 이전 값을 바꿔 끼워요. 그래서 light를 mut로 선언했어요. #[derive(Debug)]는 구조체에서와 똑같이 열거형에서도 동작해서, {:?}가 배리언트 이름을 출력해 줘요.
로직을 메서드에 넣으면 그 로직이 타입 바로 옆에 있게 돼요. Light를 쓰는 사람은 신호 시간이 어떻게 정해지는지 몰라도 light.seconds()를 호출하기만 하면 돼요. 그리고 나중에 FlashingYellow 같은 새 배리언트를 추가하면, 고쳐야 할 match를 컴파일러가 하나하나 짚어 줘요.
Option: 이미 써 온 열거형
섹션 제목: “Option: 이미 써 온 열거형”별도 페이지에서 제대로 배우게 될 Option 타입은 사실 표준 라이브러리에 정의된 평범한 열거형이에요.
enum Option<T> { Some(T), None,}열거형을 이해하고 나면 Option이 더 이상 특별한 문법처럼 보이지 않을 거예요. 배리언트가 두 개 있고, 그중 하나가 값을 담고 있을 뿐인 아주 평범한 열거형이에요.
그냥 “종류” 필드가 있는 구조체를 쓰면 안 될까요?
섹션 제목: “그냥 “종류” 필드가 있는 구조체를 쓰면 안 될까요?”구조체에 "circle"이나 "rectangle" 같은 문자열 필드를 두고 흉내 내는 방법도 떠올릴 수 있어요. 하지만 그러면 실수로 "circl"이라고 쳐도 막을 방법이 없고, 종류마다 어떤 필드가 실제로 의미가 있는지 직접 확인해야 해요. 열거형을 쓰면 잘못된 조합은 애초에 작성할 수조차 없고, 컴파일러가 모든 배리언트를 처리하도록 강제해 줘요.
연습 문제
섹션 제목: “연습 문제”1. 동전 세기
섹션 제목: “1. 동전 세기”Penny, Nickel, Dime, Quarter 배리언트를 가진 열거형 Coin을 만들어 보세요. fn value_in_cents(coin: &Coin) -> u32를 작성한 다음, 지갑 속 동전을 모두 더해 보세요. 쿼터, 다임, 니켈, 페니, 그리고 쿼터 하나 더예요. 힌트: 동전을 배열에 담고 &purse를 반복하세요.
정답 보기
enum Coin { Penny, Nickel, Dime, Quarter,}
fn value_in_cents(coin: &Coin) -> u32 { match coin { Coin::Penny => 1, Coin::Nickel => 5, Coin::Dime => 10, Coin::Quarter => 25, }}
fn main() { let purse = [Coin::Quarter, Coin::Dime, Coin::Nickel, Coin::Penny, Coin::Quarter];
let mut total = 0; for coin in &purse { total += value_in_cents(coin); }
println!("합계: {total}센트");}실행 결과:
합계: 66센트2. 삼각형 추가하기
섹션 제목: “2. 삼각형 추가하기”이 페이지의 Shape 열거형에 세 변의 길이를 담는 Triangle 배리언트를 추가해 보세요. 그리고 모든 도형에서 동작하는 perimeter 함수(도형 둘레의 길이)를 작성하세요. 힌트: 원의 둘레는 2.0 * 3.14159 * radius예요. 삼각형의 둘레는 세 변을 더한 값이에요.
정답 보기
enum Shape { Circle(f64), Rectangle(f64, f64), Triangle(f64, f64, f64),}
fn perimeter(shape: &Shape) -> f64 { match shape { Shape::Circle(radius) => 2.0 * 3.14159 * radius, Shape::Rectangle(width, height) => 2.0 * (width + height), Shape::Triangle(a, b, c) => a + b + c, }}
fn main() { let shapes = [ Shape::Circle(1.0), Shape::Rectangle(4.0, 5.0), Shape::Triangle(3.0, 4.0, 5.0), ];
for shape in &shapes { println!("{:.2}", perimeter(shape)); }}실행 결과:
6.2818.0012.00match에서 Shape::Triangle 갈래를 지워 보세요. match가 더 이상 모든 배리언트를 다루지 않으니까 Rust가 컴파일을 거부해요. 여러분이 어떤 경우도 빠뜨리지 않도록 컴파일러가 챙겨 주는 거예요.
3. 온도
섹션 제목: “3. 온도”각각 f64를 담는 두 배리언트 Celsius와 Fahrenheit를 가진 열거형 Temperature를 만들어 보세요. to_celsius(&self) -> f64 메서드를 추가하고, Celsius(25.0), Fahrenheit(212.0), Fahrenheit(50.0)을 섭씨(°C)로 소수점 한 자리까지 출력하세요. 힌트: 변환 공식은 (f - 32.0) * 5.0 / 9.0이에요.
정답 보기
enum Temperature { Celsius(f64), Fahrenheit(f64),}
impl Temperature { fn to_celsius(&self) -> f64 { match self { Temperature::Celsius(c) => *c, Temperature::Fahrenheit(f) => (f - 32.0) * 5.0 / 9.0, } }}
fn main() { let readings = [ Temperature::Celsius(25.0), Temperature::Fahrenheit(212.0), Temperature::Fahrenheit(50.0), ];
for t in &readings { println!("{:.1} °C", t.to_celsius()); }}실행 결과:
25.0 °C100.0 °C10.0 °Cself를 대여했기 때문에 c와 f는 참조자(&f64)예요. 계산은 참조자로도 바로 할 수 있지만, 섭씨 값을 그대로 돌려주려면 “c가 가리키는 숫자”라는 뜻의 *c를 써야 해요.
- 열거형은 이름 붙은 여러 배리언트 중 하나인 타입을 정의해요:
enum Direction { North, South, ... }. - 배리언트는 자기만의 데이터를 담을 수 있고, 배리언트마다 서로 다른 데이터를 담을 수도 있어요.
match는 열거형의 데이터를 자연스럽게 분해하고, 모든 배리언트를 처리하도록 요구해요.- 열거형도 구조체처럼
impl블록에 메서드를 가질 수 있어요. Option(그리고 나중에 배울Result)은 표준 라이브러리에 있는 평범한 열거형이에요. 마법 같은 건 없어요.
이해도 점검
섹션 제목: “이해도 점검”이해도 점검
1. 다른 언어의 일반적인 열거형과 달리, Rust 열거형의 각 배리언트는 무엇을 할 수 있나요?
2. Direction의 match 예제에서 나머지를 모두 받는 _ 갈래가 필요 없는 이유는 무엇인가요?
3. 표준 라이브러리에서 Option<T>는 실제로 무엇으로 구현되어 있나요?
점수: 0 / 3
더 알아보기
섹션 제목: “더 알아보기”- 열거형 정의하기 — The Rust Programming Language 한국어판: 데이터를 담는 배리언트, 열거형의 메서드, 그리고 Rust에 null 대신
Option이 있는 이유를 설명해요. - match 제어 흐름 구조 — The Rust Programming Language 한국어판: 동전 분류 예제와 나머지를 모두 받는 패턴으로 열거형 매칭을 깊이 있게 다뤄요.
- Enums — Rust by Example (영어):
Click { x: i64, y: i64 }처럼 이름 붙은 필드를 가진 배리언트를 포함해, 실행 가능한 예제를 볼 수 있어요. - Enumerations — The Rust Reference (영어): 배리언트에 숫자 값을 직접 지정하는 방법 등 전체 규칙을 볼 수 있어요.
Option— 표준 라이브러리 문서 (영어): 가장 많이 쓰게 될 열거형의 실제 정의와 모든 도우미 메서드를 볼 수 있어요.