본문 바로가기

러스트의 열거형(Enums) 살펴보기

기술적인 이야기/기타 개발 2020. 10. 4.
반응형

열거형은 일반적으로 상태(state)를 정의하기 위한 타입입니다. 하나의 타입에 여러 선택 가능한 값을 정의해두고 이 값들 중 하나를 선택할 수 있게 하기 위한 특수한 타입이지요. 러스트(Rust)도 열거형 타입을 지원하며 그 목적도 비슷합니다. 다만 각 값에 별도로 가질 수 있는 독립된 추가 값을 정의할 수 있다는 점이 독특한데 스위프트(Swift)의 열거형과도 비슷한 느낌을 받았습니다.

열거형(Enums)

러스트의 열거형은 아래와 같은 형식으로 정의할 수 있습니다.

enum Gender {
    Male,
    Female,
    Other,
}

위 코드는 세 가지 값 즉 Male, Female, Other라는 세 가지 값 중 하나를 가질 수 있는 Gender라는 타입을 정의하는 예제입니다.

다른 언어에 비해 약간 특수한 점이 있다면 각 값 이름들은 대문자로 시작하는 것을 권장한다는 점이겠네요. 소문자로 시작하는 것을 권장하는 스위프트와는 약간 다릅니다. 물론 권장이기 때문에 꼭 따를 필요는 없습니다.

열거형의 사용 및 비교

사용 방법은 단순합니다. 열거형은 하나의 상태를 정의하기 위한 타입이기 때문에 바로 각 값 자체를 그대로 시용할 수 있습니다.

let gender = Gender::Female;

위 코드는 Gender 타입을 이용해 여성을 의미하는 성별을 특정 변수는 담는 예제입니다.

그렇다면 해당 변수가 어떤 상태인지 확인은 어떻게 할 수 있을까요? 대충 if로 비교해보면 되지 않을까요?

if gender == Gender::Female {  // ERROR
    println!("Yeoja!");
}

불행히도 위 코드는 아래와 같은 빌드 오류를 냅니다.

binary operator == cannot be applied to type Gender

왜 이걸 못 하는지 이해가 안 되지만 안 된다니 어쩔 수가 없네요.

대신 if let 구문을 이용해 단순 상태 비교는 가능합니다. 아래와 같은 식이지요.

if let Gender::Female = gender {
    println!("Yeoja!");
}

이렇게 하면 genderGender::Female인 경우에 분기를 구현할 수 있습니다.

match를 이용한 비교

사실 if let을 이용해 모든 값을 비교하는 것은 해당 열거형이 클수록 귀찮아질 수밖에 없습니다. 이럴 때는 match를 이용해 분기하는 편이 편할 수 있습니다.

match gender {
    Gender::Male => println!("Namja!"),
    Gender::Female => println!("Yeoja!"),
    _ => println!("Molla!")
}

match는 switch-case문과 비슷하게 쓰입니다. match에 대해서는 러스트의 제어구조 글에서도 언급하고 있으니 참고합시다.

별도의 값을 가질 수 있는 값(?)

열거형의 각 값은 자신만의 별도의 데이터를 가질 수 있습니다.

enum Gender {
    Male,
    Female,
    Special(String),
    Other,
}

위의 경우 Special이라는 값은 별도의 문자열을 데이터로 가질 수 있습니다.

따라서 아래와 같이 값과 함께 데이터도 선언해서 변수에 담을 수 있습니다.

let gender = Gender::Special(String::from("Secret"));

이렇게 값에 데이터를 담으면 아래와 같은 식으로 match 시 꺼내올 수 있습니다.

match gender {
    Gender::Special(name) => println!("{}", name),
    _ => println!("Molla")
}

혹은 if let을 통해서도 열거형 값의 데이터를 꺼내올 수 있습니다.

if let Gender::Special(name) = gender {
    println!("{}", name);
}

이런 열거형 값의 데이터는 다양한 형식으로 표기할 수 있습니다. 하나 이상의 값을 가질 수도 있고, 튜플 형식이 아닌 이름 없는 구조체 형식으로 표현하는 것도 가능합니다.

enum Gender {
    Male,
    Female,
    Special(String),
    DNA { nth: i32, value: i32 },
    All ( i32, i32 ),
    Other,
}

사실상 전부 데이터는 구조체 형식으로 가진다고 볼 수도 있습니다. 구조체 중에서는 튜플 구조체라는 특수한 형태도 있으니깐요. 잘 모른다면 구조체에 관한 별도의 글을 참고할 수 있습니다.

구조체 형식의 데이터를 가지는 값은 앞서 언급된 방식이 아닌 구조체를 벗겨내는 방식으로 데이터를 읽어오는 것이 가능합니다.

let gender = Gender::DNA { nth: 10, value: -2 };

if let Gender::DNA { nth, value } = gender {
    println!("nth {}, value {}", nth, value);
}

위의 예는 if let을 사용했는데 당연하게도 match에서도 비슷하게 활용이 가능합니다.

match gender {
    Gender::DNA { nth, value } => println!("nth {}, value {}", nth, value),
    _ => println!("Molla!")
}

뭘 쓰는 것은 개인의 마음이지만 대부분의 언어에서 열거형의 비교는 switch-case를 추천하는 만큼 러스트에서는 match를 사용하는 편이 좋을 것 같다고 생각됩니다.

맺음말

열거형 자체는 간단합니다. 단지 중요한 특징이 있다면 러스트로 코딩하는 도중 계속해서 만날 OptionResult 타입이 이 열거형으로 구현되어 있다는 점이겠네요. 이 두 타입은 러스트의 기본 타입들 글에서 언급하고 있으니 참고합시다. 어쨌거나 그만큼 기본기면서도 중요한 타입 정의 수단으로 볼 수 있습니다.

요즘 글을 쓰기 위한 짬을 내는 것이 힘들어지고 있습니다. 다음 업데이트는 늦어질 수 있다는 말이겠네요. 읽어주시는 모든 분들께 감사드립니다.

728x90
반응형

댓글