Rust의 enum은 매우 특별합니다. C++과 파이썬처럼 단순히 열거하고 0,1,2,3 순서대로 구분만하는것을 뛰어넘어 실제로 다양한 데이터를 저장할수있습니다. 다른언어와 차별점이 크기때문에 꼭 공부하고 넘어가야할 부분입니다.
Enum 기본 기능
우선 기본기능이자 다른언어의 enum과 같은방식으로 사용할때 예시를 보겠습니다.
enum IpAddrKind {
V4,
V6,
}
이렇게 선언해두면
let four = IpAddrKind::V4;
let six = IpAddrKind::V6;
이런식으로 변수에 저장할수있고 필요한경우 V4인지 V6인지 구분이 가능하며 실제 ip주소 구조체를 만들경우 아래처럼 사용될수있습니다. 매개변수에서 int나 String 대신 IpAddrKind 자료형이 표시되기때문에 해당 매개변수가 어떤값을 갖는지 한눈에 볼수있고, 다른형태의 값은 받지않도록 제어가 가능해집니다. 이게 다른언어에서 사용하는 기본 enum 형태입니다.
enum IpAddrKind {
V4,
V6,
}
struct IpAddr {
kind: IpAddrKind,
address: String,
}
let home = IpAddr {
kind: IpAddrKind::V4,
address: String::from("127.0.0.1"),
};
let loopback = IpAddr {
kind: IpAddrKind::V6,
address: String::from("::1"),
};
Enum 변수에 데이터 저장
Rust에서는 같은 enum 타입에서도 내부의 값을 다르게 저장할 수 있습니다. 아래 두 개의 예시코드를 보고 데이터 저장방식을 이해해봅니다. 함수에서 IpAddr 타입의 매개변수를 받은경우, 내부 값 형태가 서로 다를수있습니다.
같은 종류의 데이터 저장
enum IpAddr {
V4(String),
V6(String),
}
let home = IpAddr::V4(String::from("127.0.0.1"));
let loopback = IpAddr::V6(String::from("::1"));
서로 다른 종류의 데이터 저장
enum IpAddr {
V4(u8, u8, u8, u8),
V6(String),
}
let home = IpAddr::V4(127, 0, 0, 1);
let loopback = IpAddr::V6(String::from("::1"));
Enum + Struct 사용예시
Enum에 서로다른 타입의 데이터를 보관할수 있으니, 구조체와 함께사용하면 더 강력해집니다. C++에서는 보통 class 객체의 상태나, 종류를 구분하기위해 enum을 많이 사용합니다. 물론 Rust에서도 그렇게 단순한용도로 사용할수도 있습니다. 아래는 enum 타입이 서로다른 데이터를 보관할수있는 점을 활용하여 만든 Message 예시입니다. 함수에서 Message를 매개변수로 받아서 여러기능을 한번에 처리할수있게 해줍니다. 먼저 아래 코드를 봅시다.
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}
Quit은 데이터를 저장하지않고, Move는 해당좌표로 이동하는 기능을 처리할수있습니다. String값을 저장할수도있고 튜플형태로 여러개의 값도 저장할수 있습니다.
struct QuitMessage; // unit struct
struct MoveMessage {
x: i32,
y: i32,
}
struct WriteMessage(String); // tuple struct
struct ChangeColorMessage(i32, i32, i32); // tuple struct
자, 이제 Message를 함수 매개변수로 받아보면 어떻게 될까요? Rust의 enum 타입의 강력함이 느껴지시나요? Message 타입마다 보관중인 데이터 타입이 다르므로, C++에서 아래코드를 구현하려면 함수를 여러개로 구분하거나, 매개변수를 최대 Message타입갯수만큼 늘려서 골라써야하는 번거로움이 있는데, 이게 한번에 처리가 가능합니다.
어때요? 대단하지않나요? 물론 클래스 상속기능을 이용해서 Message 부모클래스를 두고 자식클래스를 여러개로 만든다음 추상화를이용해 함수 하나로 처리할수있지만, 결국 그런경우 자식클래스 메소드 여러개를 따로따로 구현해야하는 불편함은 여전히 남아 있습니다.
Rust를 공부하면서 와 이거 진짜 좋다 처음 느낀게 enum이라 극찬을 남기고 지나가봅니다.
fn process_message(msg: &Message) {
match msg {
Message::Quit => {
println!("프로그램을 종료합니다. 👋");
}
// 참조로 받았기 때문에 x, y도 각각 i32의 참조인 &i32가 됩니다.
// 하지만 i32는 Copy 타입이라서 자동으로 값이 복사되어 쓰입니다.
Message::Move { x, y } => {
println!("좌표 이동: x={}, y={}", x, y);
}
// String은 Copy 타입이 아니므로, text는 &String 타입이 됩니다.
// 소유권을 가져오지 않고 문자열을 읽기만 합니다.
Message::Write(text) => {
println!("메시지 작성: {}", text);
}
Message::ChangeColor(r, g, b) => {
println!("색상 변경: R={}, G={}, B={}", r, g, b);
}
}
}
fn main() {
let m = Message::Write(String::from("안녕, 러스트!"));
// m의 참조(&m)를 전달합니다.
process_message(&m);
// 참조로 전달했기 때문에 m의 소유권이 살아있어 여기서 또 사용할 수 있습니다!
println!("아직 m을 사용할 수 있어요.");
}
The Option Enum – 값이 있을수도 혹은 없을수도
구조체를 미리 만들어뒀는데 값이 중간에 생기는 경우도 존재합니다. 따라서 어떤값은 아직 null값일수있는데 Rust에서는 null이 없기때문에 Rust만의 방식으로 풀어낸 기능입니다. Rust에서 null이 없는 이유는 null값을 참조하다가 프로그램이 종료되는 버그가 다른프로그램에서는 많이 일어나는데, 이를 컴파일러단계에서 막기위해 새로운방식을 고안해 낸것입니다.
enum Option<T> {
None,
Some(T),
}
Option타입은 Rust에 기본적으로 정의되어있습니다. Optional한 매개변수를 작성할떄 효과적입니다. C/Java 등에서는 값이 없음을 알리기위해 아래처럼 -1이나 0값을 사용하기도하는데, 사용자가 이를 모르면 에러가 발생하기 쉽고, 어떤변수는 음수값을 사용할수도있어서 고정된값을 사용할수도 없습니다. 이런식의 설정값이 많으면 문서를 만들어 매번 찾아야하고 실수가 발생하기 쉽습니다.
struct Config {
port: i32, // -1이면 "값이 없음"으로 간주하기로 약속
}
fn start_server(config: Config) {
if config.port == -1 {
println!("포트 설정이 없으므로 기본 8080을 사용합니다.");
} else {
println!("{}번 포트로 시작합니다.", config.port);
}
}
Rust에서도 이런식의 코드가 작동을 하지만 안좋은 패턴으로여겨집니다.
Option<T> 함수에 바로 사용
fn whatis(test: &Option<String>) {
match test { // &를 붙여 참조로 매칭
Some(s) => println!("{}", s),
None => println!("None"),
}
}
fn main() {
let test = Some("Hello".to_string());
whatis(&test);
whatis(&None);
}
Option<T> 구조체로 묶어서 사용
struct UserProfile {
username: String,
age: Option<u32>, // 나이는 모를 수도 있음 (None 가능)
}
fn print_profile(user: UserProfile) {
println!("이름: {}", user.username);
// age가 Option이므로 반드시 체크해야 함
match user.age {
Some(a) => println!("나이: {}", a),
None => println!("나이: 알 수 없음"),
}
}