티스토리 뷰

Rust-Language

열거형

kmj24 2021. 4. 9. 00:18

열거형

enum 키워드를 이용하여 작성한다.

구조체보다 유용하고 적절하게 사용될 상황이 있다.

예를들면 IP주소의 버전을 다룰때를 생각해봤을때, ipv4, ipv6가 있다.

모든 가능한 값들을 나열할 수 있으며 이를 열거라고 한다.

IP주소는 v4, v6 중 하나이며 동시에 두 버전이 될 수 없다. 이 경우 열거형이 적절하다.

열거형의 값은 variants 중 하나만 사용할 수 있다.

IP주소라는 같은 카테고리 내 다른버전일 경우에 대한 예시이다.

enum IpAddrKind{
    V4, // variants
    V6
}

IpAddrKind라는 열거형 타입이고, 각각 ipv4, ipv6를 나타낸다.

열거형 값

enum IpAddrKind{
    V4, // variants
    V6
}
pub fn run(){
    let ipv4 = IpAddrKind::V4;
    let ipv6 = IpAddrKind::V6;  
    route(ipv4);
    route(ipv6);
}

fn route(ip_type : IpAddrKind){}

여기서 ipv4와 ipv6는 다르지만 동일한 타입이므로 IpAddrKind라는 타입에 모두 사용할 수 있다.

 

열거형의 각 variant에 데이터를 입력하는 방법이 있다.

enum IpAddrKind{
    V4, // variants
    V6
}
struct IpAddr{
    kind: IpAddrKind,
    address: String
}

pub fn run(){
    let home = IpAddr{
        kind: IpAddrKind::V4,
        address: String::from("127.0.0.1")
    };
    let loopback = IpAddr{
        kind: IpAddrKind::V6,
        address: String::from("::1")
    };
}

구조체를 정의하고 구조체의 필드에 선언한 열거형IpAddrKind를 정의하여 각각에 대한 정보를 address필드에 담는 방법이다.

하지만 더 간결하게 표현이 가능하다.

enum IpAddr{
    V4(String),
    V6(String)
}

pub fn run(){
    let home = IpAddr::V4(String::from("127.0.0.1"));
    let loopback = IpAddr::V6(String::from("::1"));
}

 

열거형의 각 variant에 직접 데이터를 붙임으로써, 구조체를 사용할 필요가 없다.

또한 각 variant는 다른 타입과, 다른 양의 연관된 데이터를 가질 수 있다.

enum IpAddr{
    V4(u8, u8, u8, u8),
    V6(String)
}
pub fn run(){

    // let home = IpAddr::V4(String::from("127.0.0.1"));
    let home = IpAddr::V4(127, 0, 0, 1);
    let loopback = IpAddr::V6(String::from("::1"));

}

열거형 타입

열거형에는 어떤 종류의 데이터타입이라도 사용할 수 있다.

enum Message{
    Quit, //연관된 데이터가 없음
    Move{x: i32, y: i32}, //익명 구조체를 포함
    Write(String), //하나의 String을 포함
    ChnageColor(i32, i32, i32) //3개의 i32 포함
}

구조체의 경우 각기 다른 타입을 갖는 여러 구조체를 사용한다면, 어떤 한가지를 argument로 넘겨주는 함수를 정의하기 복잡해질 것이다. 열거형은 하나의 타입으로 가능하다.

 

메소드 정의

구조체와 유사하게 impl을 사용하여 method를 정의할 수 있다.

구조체와 마찬가지로 #[derive(Debug)]를 열거형 위에 작성해주어야 한다.

#[derive(Debug)]
enum Message{
    Quit,
    Move{x: i32, y: i32},
    Write(String),
    ChnageColor(i32, i32, i32)
}
pub fn run(){
    let m = Message::Write(String::from("hello"));
    let m2 = Message::Write(String::from("hi"));
    m.call();
    m.res(&m2);
}
impl Message{
    fn call(&self){
        println!("{:?}", self);
    }
    fn res(&self, other : &Message){
        println!("m : [{:?}], m2 : [{:?}]", self, other);
    }
}

 

Option열거형

Option타입은 값이 있을 수도, 없을 수도 있는 상황에서 사용할 숭 있다.

타입 시스템의 관점으로 컴파일러가 모든 경우를 처리했는지 확인할 수 있고, 버그를 방지할수 있다.

Option<T> 열거형이다.

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

Option열거형은 여전히 열거형이며, Some(T), None은 variante이다.

Some과 None 이외의 variante는 추가할 수 없고 제너릭 타입 파라미터인 T는 이름을 바꿔도 됨.

enum Option<T> {
    Some(T),
    None
}
pub fn run(){
    let some_number = Some(5);
    let some_string = Some("a string");
    let absend_number: Option<i32> = Option::None;
}

Some이 아닌 None을 사용한다면 Option<T>가 어떤 타입을 가질지 rust에 알려줘야 한다. 그렇지 않으면 Some variant의 타입이 어떤건지 추론할 수 없기 때문이다.

Option<T>와 T는 다른타입이며, 컴파일러는 Option<T>값을 명확하게 유효한 값처럼 사용하지 못하도록 한다.

Option은 값이 Some에 해당할때도 일반 변수타입과 연산을 할수 없다는 의미다.

enum Option<T> {
    Some(T),
    None
}
pub fn run(){
    let x: i8 = 5;
    let y: Option<i8> = Option::Some(5);
    let sum = x + y;
}

위의 코드는 컴파일 되지 않는다.

연산을 수행하기 전에 Option<T>를 T로 type casting해주어야 하며, None이라면 예외처리를 해줘야 한다.

이로 인해 Option<T>열거형은 null이 아닌 None을 가짐으로, null으로 부터 발생하는 수많은 오류로부터 안전할 수 있다.

null일 수 있는 값을 사용하기 위해 명시적으로 값의 type을 Option<T>로 만들어 주고, 그 다음 값을 사용할 때 null인 경우를 처리하면 된다. 값의 타입이 Option<T>가 아닌 모든 곳은 값이 null이 아니라고 안전히 가정할 수 있고, null의 사용을 제한하고, 코드의 안전성을 높일 수 있다.


이를 위하여 Option<T>와 Option<T>에 대한 Method들에 익숙해지자.

공식문서 : doc.rust-lang.org/std/option/enum.Option.html

 

std::option::Option - Rust

pub enum Option { None, Some(T), } #[must_use = "if you intended to assert that this has a value, consider `.unwrap()` instead"]pub const fn is_some(&self) -> bool1.0.0 (const: 1.48.0)[src] Returns true if the option is a Some value. let x: Option = Some(2

doc.rust-lang.org

 

전체 코드

enum IpAddrKind{
    V4, // variants
    V6
}

// struct IpAddr{
//     kind: IpAddrKind,
//     address: String
// }

enum IpAddr{
    V4(u8, u8, u8, u8),
    V6(String)
}
#[derive(Debug)]
enum Message{
    Quit,
    Move{x: i32, y: i32},
    Write(String),
    ChnageColor(i32, i32, i32)
}

#[derive(Debug)]
enum Option<T> {
    Some(T),
    None
}

pub fn run(){
    let ipv4 = IpAddrKind::V4;
    let ipv6 = IpAddrKind::V6;  
    route(ipv4);
    route(ipv6);
    // let home = IpAddr{
    //     kind: IpAddrKind::V4,
    //     address: String::from("127.0.0.1")
    // };
    // let loopback = IpAddr{
    //     kind: IpAddrKind::V6,
    //     address: String::from("::1")
    // };
    // let home = IpAddr::V4(String::from("127.0.0.1"));
    let home = IpAddr::V4(127, 0, 0, 1);
    let loopback = IpAddr::V6(String::from("::1"));
    let m = Message::Write(String::from("hello"));
    let m2 = Message::Write(String::from("hi"));
    m.call();
    m.res(&m2);
    let some_number = Some(5);
    let some_string = Some("a string");
    let absend_number: Option<i32> = Option::None;
    println!("{:?}", some_number);
    println!("{:?}", some_string);
    println!("{:?}", absend_number);
    let x: i8 = 5;
    let y: Option<i8> = Option::Some(5);
    //let sum = x + y;
}
fn route(ip_type : IpAddrKind){}

impl Message{
    fn call(&self){
        println!("{:?}", self);
    }
    fn res(&self, other : &Message){
        println!("m : [{:?}], m2 : [{:?}]", self, other);
    }
}

 

참고 : rinthel.github.io/rust-lang-book-ko/ch06-01-defining-an-enum.html

 

열거형 정의하기 - The Rust Programming Language

코드를 작성할 때, 열거형이 구조체보다 유용하고 적절하게 사용되는 상황에 대해서 살펴볼 것입니다. IP 주소를 다뤄야 하는 경우를 생각해 봅시다. 현재 IP 주소에는 두 개의 주요한 표준이 있

rinthel.github.io

 

'Rust-Language' 카테고리의 다른 글

if let 흐름 제어  (0) 2021.04.13
match 흐름 제어 연산자  (0) 2021.04.09
method  (0) 2021.04.07
구조체  (0) 2021.04.07
Slices  (0) 2021.04.05
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/07   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31
글 보관함