열거형 정의
열거형은 하나의 타입이 가질 수 있는 배리언트(variant)를 열거해 타입을 정의할 수 있도록 한다. 어떤 값이 여러 개의 가능한 값의 집합 중 하나라는 것을 나타낸다.
IP주소는 ipv4, ipv6 두 가지 표준으로만 사용되므로, 열거형으로 모든 배리언트를 열거할 수 있다.
enum IpAddrKind {
V4,
V6,
}
let four = IpAddrKind::V4;
let six = IpAddrKind::V6;
열거형을 정의할 때 식별자로 네임스페이스가 만들어지므로 각 배리언트 앞에 ::
을 붙여야 한다.
enum IpAddr {
V4(String),
V6(String),
}
let home = IpAddr::V4(String::from("127.0.0.1"));
let loopback = IpAddr::V6(String::from("::1"));
열거형의 각 배리언트에 직접 데이터를 붙일 수도 있다. 열거형 배리언트의 이름이 해당 인스턴스의 생성자 함수처럼 동작하게 된다.
V4
주소에는 4개의 u8 값을 저장하길 원한다면 다음과 같이 정의할 수도 있다.
enum IpAddr {
V4(u8, u8, u8, u8),
V6(String),
}
Option 열거형
Option 타입은 값이 있거나 없을 수 있는 상황을 나타낸다.
러스트에는 NULL이 없다. NULL값을 NULL이 아닌 값처럼 사용할 때 발생하는 에러가 무수히 많기 때문이다. 하지만 ‘존재하지 않는다’라는 개념을 표현할 필요는 있다.
enum Option<T> {
None,
Some(T),
}
Option
과 그 배리언트는 러스트에서 기본으로 임포트하므로 네임스페이스를 표시할 필요가 없다.
<T>
는 제네릭 타입 매개변수로, Some
배리언트는 어떤 타입의 데이터도 담을 수 있다.
Some값을 얻으면 값이 존재하고, 해당 값이 Some 내에 있음을 알 수 있다.
None값을 얻으면 얻은 값이 유효하지 않음을 알 수 있다.
Option<T>
와 T(어떤 데이터 타입)는 다른 타입이다. Option<i8>
과 i8
을 더하려고 하면 다른 타입이므로 에러가 발생한다. 그래서 Option<i8>
을 i8
로 변환해야 한다.
그래서 실제로는 NULL인데 NULL이 아니라고 가정하는 상황을 발견하는데 도움이 된다. NULL일 수 있는 값을 사용하려면 값의 타입을 명시적으로 Option<T>
로 만들어야 한다. Option<T>
가 아닌 모든 곳은 값이 NULL이 아니라고 가정할 수 있는 것이다.
Option<T>
를 사용하려면 배리언트를 처리해야 한다.
Some(T)
값일 때만 실행되어 T
를 사용하는 코드, None
값일 때만 실행되어 T
값을 쓸 수 없는 코드도 필요하다.
match 제어 흐름
match는 일련의 패턴에 대해 어떤 값을 비교한 뒤 어떤 패턴에 매칭되었는지를 바탕으로 코드를 수행한다.
enum Coin {
Penny,
Nickel,
Dime,
Quarter,
}
fn value_in_cents(coin: Coin) -> u8 {
match coin {
Coin::Penny => 1,
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter => 25,
}
}
위 함수는 Coin
열거형 타입 변수 coin
의 배리언트 값에 따라 정수를 반환하는 함수이다.
swicth ~ case문과 유사하게 흘러간다 볼 수 있다.
바인딩
match는 패턴과 매칭된 값들의 일부분을 바인딩할 수 있다. 이를 통해 열거형의 배리언트로부터 어떤 값을 추출할 수 있다.
#[derive(Debug)]
enum UsState {
Alabama,
Alaska,
}
enum Coin {
Penny,
Nickel,
Dime,
Quarter(UsState),
}
fn value_in_cents(coin: Coin) -> u8 {
match coin {
Coin::Penny => 1,
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter(state) => {
println!("State quarter from {:?}!", state);
25
}
}
}
미국의 쿼터 동전 디자인이 각 주마다 달랐던 적이 있다. 쿼터 동전이 주(state) 정보를 표현할 수 있게 Coin
의 Quarter
배리언트를 Quarter(UsState)
로 변경했다.
이제 주 정보를 갖는 Coin
열거형의 Quarter
배리언트에 대해서는 Coin::Quarter(state)
가 매치되고, state
에 바인딩된 정보를 얻어낼 수 있다.
Option를 이용한 매칭
fn plus_one(x: Option<i32>) -> Option<i32> {
match x {
None => None,
Some(i) => Some(i + 1),
}
}
let five = Some(5);
let six = plus_one(five);
let none = plus_one(None);
Option<i32>
를 매개변수로 받아서 내부에 값이 있으면 1을 더하고, 없으면 None을 반환한다.
match를 사용할 때 갈래의 패턴은 모든 가능한 경우를 다루어야 한다. 위에서 None
케이스를 다루지 않으면 버그가 발생한다.
포괄 패턴과 _자리표시자
열거형의 일부 값에 대해서는 특별한 동작을 하고, 그 외의 값에 대해서는 기본 동작을 취하도록 할 수 있다. switch case의 default와 유사하다.
주사위를 굴려서 1이 나오면 멋진 모자를 주고, 2가 나오면 모자를 없애고, 나머지가 나오면 플레이어를 움직인다고 하면 다음과 같이 구현할 수 있다.
let dice_roll = 5;
match dice_roll {
1 => add_fancy_hat(),
2 => remove_fancy_hat(),
other => move_player(other),
}
fn add_fancy_hat() {}
fn remove_fancy_hat() {}
fn move_player(num_spaces: u8) {}
match에 특별하게 나열되지 않은 나머지 모든 값은 other
에 의해 처리된다. 이런 포괄(catch-all) 패턴을 통해 모든 경우를 나열할 수 있다. 포괄 갈래를 앞에 두면 그 뒤에 있는 갈래는 결코 실행될 수 없다.
만약 포괄 패턴의 값을 사용할 필요가 없다면 _
를 사용한다. 어떠한 값을 매칭하지만 바인딩하지는 않는다.
if let
let config_max = Some(3u8);
match config_max {
Some(max) => println!("The maximum is configured to be {}", max),
_ => (),
}
위 코드는 config_max가 Some값을 지니면 그 값을 출력하는 match 문이다. None값에 대해서는 아무 동작도 시행하지 않을 것이고, 이를 포괄 패턴으로 처리했다.
하나의 패턴만 매칭시키려 했는데, 나머지 패턴도 처리해줘야 하므로 코드가 길어졌다.
let config_max = Some(3u8);
if let Some(max) = config_max {
println!("The maximum is configured to be {}", max);
}
if let
구문을 사용하면 동일한 동작을 더 짧게 작성할 수 있다.
Some(max) = config_max
는 match문의 첫 번째 갈래와 일치한다.
더 간편하지만, match에서만큼 철저하게 검사를 하진 않는다. 한 패턴에 매칭될 때만 코드를 실행하고 다른 경우는 무시하는 match
문을 작성할 때 사용하는 syntax sugar이다.
else를 추가해 포괄 패턴과 동일한 역할을 수행하게 할 수도 있다.
'프로그래밍 > Rust' 카테고리의 다른 글
[Rust] 컬렉션 (0) | 2024.05.18 |
---|---|
[Rust]프로젝트 모듈 관리 (0) | 2024.05.17 |
[Rust] 구조체 (0) | 2024.05.16 |
[Rust] 소유권 (0) | 2024.05.16 |
[Rust] 변수, 함수, 조건문, 반복문 (0) | 2024.05.16 |