열거형(enum)
C#의 enum은 기호 이름을 정수 값에 대응시키는 값 타입입니다.
enum DayOfWeek
{
Sunday = 0,
Monday = 1,
Tuesday = 2,
Wednesday = 3,
Thursday = 4,
Friday = 5,
Saturday = 6,
}
Rust에서도 거의 같은 구문으로 열거형을 정의합니다.
enum DayOfWeek
{
Sunday = 0,
Monday = 1,
Tuesday = 2,
Wednesday = 3,
Thursday = 4,
Friday = 5,
Saturday = 6,
}
.NET과 달리 Rust의 enum 인스턴스에는 상속받는 기본 동작이
없습니다. 처음에는 dow == DayOfWeek::Friday 같은 동등성 비교도
할 수 없습니다. C#의 enum에서 흔히 쓰는 기능을 갖추려면
#[derive] 특성으로 필요한 구현을 자동 생성합니다.
#[derive(Debug, // "{:?}" 형식 출력을 지원합니다.
Clone, // Copy에 필요합니다.
Copy, // 값 복사 의미론을 지원합니다.
Hash, // 맵 타입에서 사용할 해시 계산을 지원합니다.
PartialEq // 값 동등성 비교(==)를 지원합니다.
)]
enum DayOfWeek
{
Sunday = 0,
Monday = 1,
Tuesday = 2,
Wednesday = 3,
Thursday = 4,
Friday = 5,
Saturday = 6,
}
fn main() {
let dow = DayOfWeek::Wednesday;
println!("Day of week = {dow:?}");
if dow == DayOfWeek::Friday {
println!("Yay! It's the weekend!");
}
// 정수로 변환합니다.
let dow = dow as i32;
println!("Day of week = {dow:?}");
let dow = dow as DayOfWeek;
println!("Day of week = {dow:?}");
}
위 예제처럼 열거형을 지정된 정수 값으로 변환할 수 있습니다. 반대
방향의 변환은 C#처럼 바로 할 수 없습니다. C#/.NET에서는 표현하지
않은 정수 값이 enum 인스턴스에 들어갈 수 있다는 단점도 있습니다.
Rust에서는 변환을 위한 보조 함수를 직접 만들 수 있습니다.
impl DayOfWeek {
fn try_from_i32(n: i32) -> Result<DayOfWeek, i32> {
use DayOfWeek::*;
match n {
0 => Ok(Sunday),
1 => Ok(Monday),
2 => Ok(Tuesday),
3 => Ok(Wednesday),
4 => Ok(Thursday),
5 => Ok(Friday),
6 => Ok(Saturday),
_ => Err(n)
}
}
}
try_from_i32 함수는 n이 올바르면 성공을 나타내는 Ok와
DayOfWeek를 Result로 반환합니다. 그렇지 않으면 실패를
나타내는 Err에 원래 n을 담아 반환합니다.
let dow = DayOfWeek::try_from_i32(5);
println!("{dow:?}"); // 출력: Ok(Friday)
let dow = DayOfWeek::try_from_i32(50);
println!("{dow:?}"); // 출력: Err(50)
정수 타입과 열거형 사이의 변환을 직접 구현하지 않아도 되도록 돕는 Rust crate도 있습니다.
Rust의 enum으로는 각 변형이 서로 다른 데이터를 담는 구분된
유니언 타입도 설계할 수 있습니다. 다음 예제를 살펴보겠습니다.
enum IpAddr {
V4(u8, u8, u8, u8),
V6(String),
}
let home = IpAddr::V4(127, 0, 0, 1);
let loopback = IpAddr::V6(String::from("::1"));
C#에는 같은 형태의 enum 선언이 없지만 클래스 레코드로 비슷하게
표현할 수 있습니다.
var home = new IpAddr.V4(127, 0, 0, 1);
var loopback = new IpAddr.V6("::1");
abstract record IpAddr
{
public sealed record V4(byte A, byte B, byte C, byte D): IpAddr;
public sealed record V6(string Address): IpAddr;
}
Rust의 정의는 변형의 집합이 고정된 _닫힌 타입_을 만듭니다.
컴파일러는 IpAddr에 IpAddr::V4와 IpAddr::V6 이외의 변형이
없다는 사실을 압니다. 따라서 C#의 switch 식과 비슷한 Rust의
match 식에서 모든 변형을 다루지 않으면 컴파일 오류를 냅니다.
반면 C# 레코드로 흉내 낸 형태는 간결해 보이더라도 클래스 계층을
만듭니다. IpAddr가 _추상 기본 클래스_이므로 컴파일러는 이 타입이
표현할 수 있는 모든 하위 타입을 알지 못합니다.