match
match 연산자
Rust는 각각의 값 혹은 패턴에 따라 서로 다른 분기를 이뤄 각기 다른 코드를 실행하게 하는 match 연산자를 제공하고 있다. 이 match는 (C#과 같은 프로그래밍 언어에서의) switch와 비슷한 기능을 제공한다.
match를 사용하기 위해서는, match 키워드 뒤에 분기의 소스가 되는 변수명을 적고 { ... } 괄호 안에 각 패턴별로 실행할 코드를 적으면 된다. 각 패턴별 실행코드를 지정하는 것을 갈래(arm)이라 부르는데, match 에는 보통 여러 조건들이 있으므로 하나의 match는 보통 여러 개의 갈래(arm)들을 갖는다. 예를 들어, 아래는 3개의 arm을 갖는 예를 표현한 것이다.
match variable { 패턴1 => 실행코드1, // arm1 패턴2 => 실행코드2, // arm2 패턴3 => 실행코드3, // arm3 }
match 연산자는 열거형(enum)과 함께 사용되는 경우가 많은데, 아래 예제는 함수 priority_weight()에서 Priority 열거형을 파라미터로 받아들인 후, 그 Priority값에 따라 다른 가중치(weight)값을 리턴하는 것을 표현한 것이다. 즉, High일 때는 가중치 100을 리턴하고, Medium 일 때는 가중치 10을 리턴하게 된다. 여기서의 match 패턴은 열거형의 리터럴값이었지만, 패턴에는 리터럴값, 변수명, 와일드 카드 등을 비롯하여 다양한 표현이 가능하다.
enum Priority { Low, Medium, High } fn priority_weight(priority: Priority) -> i32 { match priority { Priority::High => 100, Priority::Medium => 10, Priority::Low => { println!("Default priority"); 1 } } } fn main() { let weight = priority_weight(Priority::High); println!("{}", weight); // 100 }
위의 예제에서 각 arm의 실행코드는 하나일 경우는 직접 값을 적지만, 만약 여러 문장을 실행할 경우 { } 괄호로 묶어 표현한다. 예를 들어, Priority::Low 패턴인 경우 "Default priority"를 출력하고 1을 리턴하는 2개의 문장을 { } 괄호로 묶어 주었다.
match 패턴에서 중요한 점은 가능한 패턴 전부를 모두 지정해야 한다는 것이다. 예를 들어, 위의 예에서 match 안에 Priority::Medium을 빼고, Priority::High와 Priority::Low 패턴만을 지정하면 컴파일러 에러가 발생한다.
Option 열거형 match 표현
Option<T> 열거형에 대해 match를 사용하면, 아래 예제와 같이 None과 Some 케이스에 대해 각각 다른 코드를 실행해 줄 수 있다.
fn choice(opt: Option<i32>) { match opt { None => println!("No choice"), Some(val) => println!("#{} was chosen", val) } } fn main() { let opt: Option<i32> = Option::Some(1); choice(opt); }
Catch-all 패턴
match에서 일부 패턴만 처리하고 나머지 패턴들에 대해서는 하나의 실행코드를 실행할 수 있다 (즉, C/C#에서의 default 블럭처럼).
아래 예제는 변수 n의 값을 체크해서 0이면 "Zero", 1이면 "One" 을 출력하고 나머지 모두에 대해서는 숫자를 문자열로 변환해서 리턴하는 match 식을 표현하고 있다. 아래에서 Catch-all 패턴은 변수 x를 사용하고 있는데, 이는 임의의 변수명을 사용할 수 있다. Catch-all 패턴은 항상 match 의 마지막에 있어야 한다.
fn main() { let n: u8 = 2; let res = match n { 0 => String::from("Zero"), 1 => String::from("One"), x => x.to_string() }; println!("{}", res); }
Catch-all 패턴에서 만약 나머지의 경우 어떤 특별한 일을 할 필요가 없다면, 아래와 같이 패턴 변수명에 밑줄(_) 을 사용하고 실행 코드에 unit 타입 () 을 적으면 된다.
let res = match n { 0 => String::from("Zero"), 1 => String::from("One"), _ => () };
if let 표현
match에서 하나의 패턴에 대해 특별한 처리를 하고, 나머지를 무시하거나 혹은 나머지 전체에 대해 다른 코드 블럭을 실행하는 경우, (match 대신) "if let" 이라는 간략한 표현을 사용할 수 있다.
예를 들어, 아래와 같이 Option 매칭에서 Some인 경우만 처리하고 싶으면
fn process(opt: Option<i32>) { match opt { Some(val) => println!("#{} was chosen", val), _ => () } }
아래와 같이 if let을 써서 보다 간략히 표현할 수 있다.
fn process(opt: Option<i32>) { if let Some(val) = opt { println!("#{} was chosen", val); } }
"if let" 뒤에서는 match에서의 "Some(val)"와 같은 패턴을 먼저 적고, 다음 match하고자 하는 변수명(여기서는 opt)를 적는다. if let의 패턴이 맞는 경우 { } 블럭 안의 코드가 실행되고, 나머지 조건의 경우(match에서의 _ 조건)는 무시된다.
만약 나머지 조건에 어떤 코드를 실행하기 위해서는 if let 블럭 뒤에 else 블럭을 사용할 수 있다.
fn process(opt: Option<i32>) { if let Some(val) = opt { println!("#{} was chosen", val); } else { println!("No option"); } }