[RUST] 6. 열거자와 패턴 매칭
- 📕 Book/러스트 프로그래밍 공식 가이드
- 2022. 4. 24.
열거자(enumerations, enums)는 사용 가능한 값만 나열한 타입을 정의할 때 사용한다.
열거자 정의하기
IP주소를 예를 들어 보자. IP주소는 버전 4나 버전 6으로 두 가지가 표준으로 자리 잡고 있다.
enum IpAddrKind {
V4,
V6,
}
열거된 값들을 열것값(variants)라고 한다.
열거자의 값
let four = IpAddrKind::V4;
let six = IpAddrKind::V6;
fn route(ip_type: IpAddrKind) {}
route(IpAddrKind::V4);
route(six);
열거자의 각 값은 식별자를 이용해 구분하며, 식별자와 값을 구분하기 위해 두 개의 콜론을 사용한다. 열거자를 매개변수로 갖는 함수를 정의할수도 있고, 열거자의 값을 이용해 호출 할 수도 있다.
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,
adress: String::from("::1");
}
위는 구조체를 이용해 IP주소의 종류와 데이터를 저장한 예제이다. 하지만 열거자를 구조체에 넣지않고 열거자만을 이용하는 방법이 있다. 다음 예제를 보자.
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 Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}
열거자는 구조체와 유사점이 있는데, 열거자로 위처럼 정의하게 되면 여러 종류의 메시지를 매개변수로 전달 받는 함수를 하나만 정의해도 사용할수 있다. 추가로 impl 블록을 이용해 열거자에도 메서드를 정의할 수 있다.
impl Message {
fn call(&self) {
// 메서드 본문 작성
}
}
let m = Message::Write(String::from("hello"));
m.call();
Option 열거자를 Null값 대신 사용할 때의 장점
러스트는 널값이라는 개념이 없지만, 어떤 값의 존재 여부를 표현하는 열거자를 정의하고 있다. 이 열거자가 바로 Option< T >이며 표준 라이브러리에 다음과 같이 정의되어 있다.
enum Option<T> {
Some(T),
None,
}
Option< T > 열거자는 프렐류드에 포함되어 있다. 열거값도 명시적으로 범위로 가져올 필요가 없다.
let some_number = Some(5);
let some_string = Some("a string");
let absent_number: Option<i32> = None;
Option< T >와 T는 서로 다른 타입이기 때무에 컴파일러는 유효한 값이 존재할 때는 Option< T >값을 사용하지 않도록 하고 있다. Option< T > 값을 사용하려면 열거자에 나열된 개별 값들을 처리할 코드를 작성해야 한다. match 표현식은 이런 코드를 쉽게 작성할 수 있는 '흐름 제어 연산자(control flow operator)'이다.
match 흐름 제어 연산자
macth는 러스트의 강력한 흐름제어 연산자이다. 이 연ㄴ산자는 일련의 패턴과 값을 비교해 일치하는 패턴에 지정된 코드를 실행한다. 패턴은 리터럴, 변수명, 와일드 카드를 비롯해 다양한 값으로 구성할 수 있다. match 표현식은 동전을 분리하는 기계라고 생각하면 된다.
enum Coin {
Penny,
Nickle,
Dime,
Quarter,
}
fn value_in_cents(coin: Coin) -> u32 {
match coin {
Coin::penny => 1,
Coin::Nickle => 5,
Coin::Dime => 10,
Coin::Quarter => 25,
}
}
값을 바인딩하는 패턴
match 표현식은 패턴에 일치하는 값의 일부를 바인딩 할 수 있다.
#[derive(Debug)]
enum UsState {
Alabama, Alaska, ...
// 생략
}
enum Coin {
Penny,
Nickel,
Dime,
Quarter(UsState),
}
fn value_in_cents(coin: Coin) -> u32 {
match coin {
Coin::penny => 1,
Coin::Nickle => 5,
Coin::Dime => 10,
Coin::Quarter(state) => {
println!("State quarter from {:?}!", state);
25
},
}
}
위 코드는 match 표현식에서 패턴에 state변수를 추가해서 Coin::Quarter 열것값에 state 변수가 자동 바인딩 된다.
Option< T >를 이용한 매칭
Option< T >는 Coin 열거자의 사용처럼 match 표현식에도 사용할 수 있다.
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_on(None);
match는 반드시 모든 경우를 처리해야 한다
fn plus_one(x: Option<i32>) -> Option<i32> {
match x {
come(i) => some(i + 1),
}
}
위와 같은 코드를 작성했다면 None 값에 해당하는 경우를 처리하지 않았기 때문에 컴파일시 에러가 발생한다.
자리지정자 _
별도의 처리가 필요하지 않을 때는 _ 자리지정자로 대체하면 모든값이 일치함을 의미 하므로 나머지 모든 패턴에 대해 처리하게 된다.
let some_u8_value = 0u8;
match some_u8_value {
1 => println!("one"),
3 => println!("three"),
5 => println!("five"),
7 => println!("seven"),
_ => (),
}
if let을 이용한 간결한 흐름 제어
if let 문법은 하나의 패턴과 표현식을 등호 기호를 이용해 조합한다. match 표현식과 같은 방식으로 동작한다. if let 구문은 else 구문을 포함할 수 있다.
if let Some(3) = some_u8_value {
println!("three");
}
let mut count = 0;
if let Coin::Quarter(state) = coin {
println!("{:?}주의 25센트 동전!", state);
} else {
count += 1;
}
요약
- 열거자를 사용해 사용자 정의 타입을 생성하는 방법
- 표준 라이브러리의 Option< T > 타입을 이용해 오류 방지 기능 활용
- match와 it let 표현식의 활용법