ENUM 이란?
Rust에서 enum은 열거형 타입으로, 여러 개의 다른 값들 중 하나의 값을 가질 수 있는 타입이다.
Enumeration (열거) 에서 기원한 키워드이다.
이는 Rust의 강력한 타입 시스템과 패턴 매칭 기능을 활용하여
다양한 종류의 데이터를 안전하고 효율적으로 처리할 수 있게 해준다.
ENUM 의 특징
1) 다양한 값 정의가 가능
enum을 사용하여 서로 다른 값들을 정의할 수 있다.
각각의 값은 enum 타입의 '변종(variant)'이라고 불린다.
예를 들어, 트래픽 신호등의 상태를 나타내는 enum을 정의할 수 있다. (Red, Yellow, Green.)
#[derive(Debug, PartialEq)]
enum Color {
Red,
Green,
Blue
}
fn main() {
let red_col = Color::Red;
let green_col = Color::Green;
println!("red_col == green_col : {:?}", red_col == green_col);
println!("red_col == red_col : {:?}", red_col == red_col);
println!("red_col == Color::Red : {:?}", red_col == Color::Red);
}
2) 패턴 매칭 (Pattern matching)
enum과 함께 패턴 매칭을 사용하면 코드의 안전성과 가독성이 향상된다.
match 문을 사용하여 enum의 변종을 검사하고, 각 경우에 대한 행동을 정의할 수 있다.
enum TraficLight {
Red,
Yellow,
Green
}
struct RGB(u8, u8, u8);
fn light_to_rgb(color: TraficLight) -> RGB {
// match 문을 사용해서 다양한 패턴들을 검사.
// 중요한건 모든경우의 대해서 처리를 해줘야 한다.
match color {
TrafiicLight::Red => RGB(255,0,0),
TrafiicLight::Yellow => RGB(255,165,0),
TrafiicLight::Green => RGB(0,255,255)
}
}
fn main() {
let RGB(r,g,b) = light_to_rgb(TrafiicLight::Green);
println!("r = {}, g = {}, b = {}", r,g,b);
}
위의 예제에서 light_ro_rgb() 함수는 신호등의 색을 받은 뒤
해당 색을 rgb 로 표현하는 예제이다.
light_to_rgb() 함수에서 중요한건, match color 부분에
TraficLight 에 열거된 모든 값을 비교해 줘야한다는 점이다.
만약에, 값이 하나라도 누락되면 아래와 같이 오류가 발생한다.
2) 옵션 타입 (Option Type)
Rust에서 Option<T>는 매우 중요한 enum 타입으로,
어떤 값이 있거나 없을 수 있는 상황을 나타낸다.
이는 Rust의 안전한 메모리 관리와 오류 처리에 핵심적인 역할을 한다.
Option<T>는 다음 두 가지 variant를 가진다.
1) Some(T): 값이 존재하는 경우를 나타낸다. 여기서 T는 저장된 값의 타입이다.
2) None: 값이 없음을 나타낸다.
Option<T>의 사용은 Rust에서 널 포인터(null pointer)를 사용하는
다른 언어들과 비교하여 메모리 안전성을 크게 향상시킨다.
예를 들어, 값이 없을 수도 있는 상황에서 null 대신 Option<T>를 사용함으로써,
프로그램이 런타임에 null 참조로 인한 오류를 발생시키는 것을 방지할 수 있다.
예를들어 아래와 같은 C++ 코드를 봐주자.
아래의 코드를 사용해서 컴파일한 뒤 결과를 보면 Null pointer Exception 이 발생할 것이다.
#include <iostream>
using namespace std;
const char* get_item(bool available) {
if (available) {
return "Item";
}
return nullptr; // 널 포인터 반환
}
int main() {
const char* item = get_item(false); // 널 포인터 반환됨
cout << item[0]; // 널 포인터 참조, 런타임 오류 발생
return 0;
}
하지만, 해당 코드를 RUST 를 사용해서 Option<T> 으로 처리해준다면, 안전하게 처리할 수 있다.
fn get_item(available: bool) -> Option<&'static str> {
if available {
Some("Item")
} else {
None // 값이 없음을 나타내는 None 반환
}
}
fn main() {
let item = get_item(false);
match item {
Some(i) => println!("{}", i),
None => println!("Item not available"), // None을 안전하게 처리
}
}
3) 결과 타입 (Result Type)
Result<T, E> 역시 enum으로, 연산이 성공적으로 완료될 수도 있고(Ok(T)),
에러가 발생할 수도 있는(Err(E)) 상황을 나타낸다.
Result 타입을 사용하면 다음과 같은 이점이 있다.
- 명시적인 오류 처리
Result 타입은 함수가 오류를 반환할 수 있다는 것을 명시적으로 나타낸다.
이를 통해 오류 처리를 강제하고, 더 견고한 프로그램을 작성할 수 있다.
- 타입 안전성
Result 타입을 사용하면 오류와 성공 값이 모두 타입으로 표현된다.
이는 프로그램의 타입 안전성을 높여준다.
- 컴파일 시점의 오류 검출
Rust 컴파일러는 Result 타입이 적절하게 처리되었는지 확인한다.
이를 통해 런타임 오류를 줄일 수 있다.
use std::fs::File;
use std::io::{self, Read};
fn read_file_contents(path: &str) -> Result<String, io::Error> {
let mut file = match File::open(path) {
Ok(file) => file,
Err(e) => return Err(e),
};
let mut contents = String::new();
match file.read_to_string(&mut contents) {
Ok(_) => Ok(contents),
Err(e) => Err(e),
}
}
fn main() {
match read_file_contents("example.txt") {
Ok(contents) => println!("File contents: {}", contents),
Err(e) => println!("Error reading file: {}", e),
}
}
5) 값 연결
Rust의 enum은 단순한 값 뿐만 아니라, 데이터를 연결할 수 있는 기능을 가지고 있다.
이를 통해 각 변종에 다른 타입의 데이터를 연결할 수 있다.
enum Message {
Quit, // 변종에 데이터가 연결되어 있지 않음
Move { x: i32, y: i32 }, // 익명 구조체 형태로 데이터 연결
Write(String), // 단일 String 데이터 연결
ChangeColor(i32, i32, i32), // 세 개의 i32 데이터 연결
}
fn main() {
// 각각의 변종에 해당하는 Message 인스턴스 생성
let msg = Message::Move { x: 10, y: 30 };
let quit = Message::Quit;
let write = Message::Write(String::from("hello"));
let change_color = Message::ChangeColor(0, 160, 255);
// 각 메시지 처리
match msg {
Message::Quit => {
println!("Quit message");
}
Message::Move { x, y } => {
println!("Move to x: {}, y: {}", x, y);
}
Message::Write(text) => {
println!("Text message: {}", text);
}
Message::ChangeColor(r, g, b) => {
println!("Change color to R: {}, G: {}, B: {}", r, g, b);
}
}
}
6) 확장성
enum은 프로그램이 성장하면서 새로운 variant을 추가하기 쉬워 확장성이 좋다.
위의 5) 의 예제와 같은 코드가 운영되고 있다고 가정해보자.
그런데 외부환경의 영향으로
enum Mesaage 에 하나의 variant 가 추가되어야 한다고 해보자.
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
Edit(String), // 추가된 부분
}
fn main() {
// 각각의 변종에 해당하는 Message 인스턴스 생성
let msg = Message::Move { x: 10, y: 30 };
let quit = Message::Quit;
let write = Message::Write(String::from("hello"));
let change_color = Message::ChangeColor(0, 160, 255);
let edit_str = Message::Edit(String::from("test"));
// 각 메시지 처리
match edit_str {
Message::Quit => {
println!("Quit message");
}
Message::Move { x, y } => {
println!("Move to x: {}, y: {}", x, y);
}
Message::Write(text) => {
println!("Text message: {}", text);
}
Message::ChangeColor(r, g, b) => {
println!("Change color to R: {}, G: {}, B: {}", r, g, b);
},
Message::Edit(s) => {
println!("Edited Text is {}", s);
}
}
}
위의 코드와 같이 쉽게 enum을 추가할 수 있다.
이런식으로 추가할때의 장점은 아래와 같다.
- 쉬운 추가
Enum 의 새로운 variant를 Message enum에 추가하는 것만으로 새로운 기능을 추가할 수 있다.
- 컴파일러 지원
Rust 컴파일러는 match 문에서 모든 가능한 경우를 다루었는지 확인한다.
새로운 variant을 추가하면, 컴파일러는 match 문에서 이를 처리하지 않은 부분에 대해 경고한다.
- 코드 안전성
새로운 variant를 추가해도 기존 코드의 안전성이 유지된다.
이는 Rust의 타입 안전성과 패턴 매칭 기능 덕분이다.
'RUST' 카테고리의 다른 글
[RUST] 제네릭 (Generics) (0) | 2024.01.22 |
---|---|
[RUST] Error (0) | 2024.01.03 |
[RUST] 구조체(structure) (0) | 2023.12.29 |
[RUST] RUST 메모리 관리 규칙 - Slice (0) | 2023.12.28 |
[RUST] RUST 메모리 관리 규칙 - 임대(lease) (0) | 2023.12.27 |