RUST

[RUST] ENUM

ssh9308 2024. 1. 2. 09:00
반응형

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의 타입 안전성과 패턴 매칭 기능 덕분이다.

 

 

 

 

 

 

반응형