🦀 Rust
[Rust 입문] 6. 열거형, 패턴 매칭
date
Mar 23, 2023
slug
rust-enum-pattern
author
status
Public
tags
Rust
독학
summary
Rust의 enum 타입과 강력한 패턴 매칭에 대한 설명입니다.
type
Post
thumbnail
category
🦀 Rust
updatedAt
Mar 23, 2023 11:18 AM
rust의 열거형은 열거형을 지원하는 다른 언어들처럼 사용할 수 있다.
fn main() { let four = IpAddrKind::V4; let six = IpAddrKind::V6; println!("{:?}, {:?}", four, six); } #[derive(Debug)] enum IpAddrKind { V4, V6, }
rust의 열거형엔 내부 나열한 값 (variants)에 대해 값을 직접 넣을수도 있다.
fn main() { let home = IpAddr::IPv4(127, 0, 0, 1); let loopback = IpAddr::IPv6(String::from("::1")); println!("{:?}, {:?}", home, loopback); } #[derive(Debug)] enum IpAddr { IPv4(u8, u8, u8, u8), IPv6(String), }
내부 값 (variants)에 대해 어느 타입을 넣어도 상관이 없으며 위와 같이 서로 다른 타입을 넣을 수도 있다.
이 형태는 구조체를 사용할 때 구조체 중 한가지의 값만 가져야 할 때 유용하게 사용할 수 있다.
열거형의 메소드
구조체와 유사하게 impl 블록 내에서 self 키워드를 이용해 정의한다.
fn main() { let home = IpAddr::IPv4(127, 0, 0, 1); let loopback = IpAddr::IPv6(String::from("::1")); home.who_am_i(); loopback.who_am_i(); } #[derive(Debug)] enum IpAddr { IPv4(u8, u8, u8, u8), IPv6(String), } impl IpAddr { fn who_am_i(&self) { println!("{:?}", self); } }
Option 열거형
많은 프로그래밍 언어에서 Null을 지원한다. Null의 존재는 Null 값을 Null이 아닌 것처럼 사용할 때 오류를 발생시키기 쉽다는 단점이 있다.
이런 문제 때문에 Null을 여러가지 방법으로 회피하는 경우가 종종 있다. (Java에서도 java 8부터 Optional이라는 클래스를 도입해 Null로 인해 발생하는 예외를 처리할 수 있게 되었다.)
rust에선 Null이 없다. 대신 Optional 열거형을 사용하는데 내부 라이브러리에 다음과 같이 정의되어 있다.
enum Option<T> { Some(T), None, }
값이 존재할 때에만 제네릭을 통해 특정 타입의 값을 가지고 있으며 값이 없을 때엔 None으로 표현한다.
실제로 사용할 때
Option:: 을 생략하고 사용해도 된다.let tmp1: Option<i8> = Some(8); let tmp2: Option<i8> = None;
if - else 문을 이용한 흐름 제어
위의 Option 타입은 값이 존재할수도, 존재하지 않을수도 있다. 존재할때와 존재하지 않을 때 서로 다른 동작을 해야 한다. rust의 if - else 문으로 제어를 할 수 있다.
fn main() { let val = Some(10); let k = if val.is_some() { 10 } else { 20 }; println!("k : {}", k); }
is_some() 은 Option 열거형에서 주어지는 메소드로 값이 존재하면 true를 리턴한다.rust에서 if - else 문은 표현식이므로 결과를 위처럼 다른 변수에 할당할 수도 있다.
Option 열거형에 if - else 문을 사요하는 것보단 match나 if let을 사용하는 것이 더 나을수도 있다.
match를 이용한 패턴 매칭 (흐름 제어)
rust엔 match라는 흐름 제어 연산자가 존재한다. 리터럴 값, 변수명, 와일드카드 등의 패턴을 비교하여 패턴에 따라 코드를 제어한다.
열거형으로 정의한 값을 패턴 매칭으로 제어하는 형태를 주로 사용하기 때문에 패턴 매칭을 여기서 소개한다.
fn main() { let tmp: Option<i8> = Some(8); match tmp { Some(v) => println!("ok (value : {})", v), None => println!("no"), } }
열거형에서 if 연산자보다 match 연산자를 사용하는 이유 중 하나는 if 내의 조건은 boolean을 리턴해야 하므로 boolean을 리턴하게 하는 코드가 추가적으로 들어간다. 하지만 패턴 매칭을 사용하면 해당 패턴에 따른 코드 블록을 실행한다.
화살표 뒤에 여러 줄의 코드가 온다면 중괄호로 감쌀 수 있으며 각 갈래 (패턴)에 대한 코드는 표현식이다. 따라서 match 표현식에 대한 값이 패턴 매칭에 대응되는 표현식이 된다.
fn main() { let tmp: Option<i8> = Some(8); let val: i8 = match tmp { Some(_) => 1, None => 0, }; println!("val : {}", val); }
그래서 위의 코드는 1이 출력되게 된다.
match는 들어오는 열거형에 대해 모든 케이스를 상정하게 된다. 그래서 원하지 않는 케이스를 그냥 넘겨버리면 컴파일 에러가 발생하게 된다. 나머지 상황 (if~elseif~else의 else나 switch문의 default) 에 대해서 처리할 때에는
_ 변경자 (placeholder)를 사용한다.fn main() { let s1 = number_to_song(1); let s2 = number_to_song(2); let s3 = number_to_song(3); let se = number_to_song(100); println!("val : {}, {}, {}, {}", s1, s2, s3, se); } fn number_to_song(num: u32) -> String { match num { 1 => String::from("일초라도 안 보이면"), 2 => String::from("이렇게 초조한데"), 3 => String::from("삼초는 어떻게 기다려 (이야 이야 이야 이야)"), 4 => String::from("사랑해 널 사랑해"), 5 => String::from("오늘은 말할 거야"), 6 => String::from("육십억 지구에서 널 만난 건"), 7 => String::from("럭키야 사랑해 요기조기 한눈팔지 말고 나를 봐"), 8 => String::from("팔딱팔딱 뛰는 가슴"), 9 => String::from("구해줘 오 내 마음"), 10 => String::from("십년이 가도 너를 사랑해"), _ => String::from("[매핑되지 않은 숫자입니다]"), } }
if let을 사용한 패턴 매칭 (흐름 제어)
위의 match 키워드를 사용하는 경우는 Option 타입을 사용할 때 다소 장황한 단점이 있다. 만약 한가지 상황 (패턴)을 만족할 때 (와 아닐 때에 전부)에만 코드의 흐름을 제어하고 싶을 때에는
if let 키워드를 사용할 수 있다.if let을 사용할 때엔 표현식으로 사용하지 못하는 것으로 보인다.
fn main() { let val = Some(10); if let Some(v) = val { println!("val에 값이 존재하며 그 값은 {}이다.", v); } else { println!("val에 값이 존재하지 않는다."); } }