흐름 제어 연산자
이 포스트는 Rust를 처음 공부하면서 정리한 내용입니다. 해당 포스트는 개인적으로 기억하기 위한 메모 성격에 가깝습니다. “러스트 프로그래밍 공식 가이드 (2018, 제이펍)” 서적을 참고하였으며, 러스트 공식 사이트 에서 책과 동일한 내용을 찾을 수 있습니다. 따라서 더 자세한 내용을 찾으시면 위의 링크를 참고하시면 좋습니다.
Rust는 match와 if let 이라는 강력한 흐름 제어 연산자를 제공합니다.
match
match
연산자는 패턴에 대한 풍부함도 강점이지만 컴파일러가 모든 경우의 수를 처리하고 있는제 확인할 수 있다는 장점을 가지고 있습니다. C와 비교하자면 switch
정도에 해당 되겠네요. (유연한 switch?)
enum Coin {
Penny,
Nickel,
Dime,
Quarter,
}
fn value_in_cents(coin: Coin) -> u32 {
match coin {
Coin::Penny => 1,
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter => 25,
}
}
value_in_cents
의 함수는 열거형 Coin을 인수로 받고 있습니다. 그리고 match 키워드 다음에는 비교할 표현식 을 작성합니다. 여기서 if
문과는 다른 차이점을 보이는데, match
연산자의 표현식은 모든 타입의 값을 작성할 수 있습니다. 만일 coin
의 값이 Coin::Dime
이라면 10 값을 리턴합니다. match 표현식은 실행 시점에 각 가지의 패턴과 결과를 순서대로 비교합니다.
아래의 예시는 패턴이 실행될 때 실행할 코드를 가지고 있는 예제입니다.
fn value_in_cents(coin: Coin) -> u32 {
match coin {
Coin::Penny => {
println!("Lucky penny!");
1
},
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter => 25,
}
}
값을 바인딩 하는 패턴
match
표현식의 가지 표현식이 가지는 장점은 패턴에 일치하는 값의 일부를 바인딩 할 수 있다는 점입니다. 아래의 예제는 열거자의 값을 추출하는 방법을 보여주고 있습니다.
#[derive(Debug)] // So we can inspect the state in a minute
enum UsState {
Alabama, Alaska, ... // 생략
}
enum Coin {
Penny,
Nickel,
Dime,
Quarter(UsState),
}
fn value_in_cents(coin: Coin) -> u32 {
match coin {
Coin::Penny => 1,
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter(state) => {
println!("State quarter from {:?}!", state);
25
},
}
}
앞서 작성한 Coin
와는 다르게, Coin::Quarter
는 UsState
타입 정보를 가지고 있습니다. 새로 작성한 match
에서는 패턴에 state
변수를 추가(바인딩, Binding)해서 사용 가능합니다.
value_in_cents(Coin::Quater(UsState::Alaska));
와 같이 호출하는 경우 coin
변수는 Coin::Quarter(UsState::Alaska)
의 값을 가지게 되고, state
변수는 USSTATE::Alaska
값이 바인딩됩니다.
주의할 점
값을 match
를 이용하여 바인딩 하는 경우 기본적으로 move 또는 copy 를 통해 바인딩됩니다. Reference를 바인딩 하는 경우 ref
키워드를, 가변적인 reference 변수를 사용하고 싶으면 mut ref
키워드를 사용합니다.
Option 를 이용한 매칭
이전 포스트에서 Option<T>
타입은 Null을 표현하기 위해 사용되는 표준 라이브러리이고 그 안의 Some(T)
의 값을 꺼내 사용할 수 있다고 했습니다. Option
열거자의 경우에도 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_one(None);
plus_one
함수는 Option<i32>
타입을 인수로 받고 Option<i32>
타입의 값을 리턴하고 있습니다. match
표현식에서 x
의 값이 None
인지, 혹은 Some
인지 검사하고 Some
인 경우에는 i
에 바인딩하여 Some(i + 1)
의 값을 반환하고 있습니다.
이처럼 Rust에서 표현식과 열거자를 조합하는 경우는 많은 상황에서 유용하게 사용되며, Rust 코드에서도 이러한 패턴을 자주 확인할 수 있습니다.
주의할 점: match는 반드시 모든 경우를 처리해야 한다!
match
표현식에서는 반드시 모든 경우를 처리해야 합니다. 즉, 패턴 매칭은 완전해야 합니다. 만일 아래와 같이 일부의 경우를 처리하지 않는다면 컴파일조차 되지 않습니다.
fn plus_one(x: Option<i32>) -> Option<i32> {
match x {
Some(i) => Some(i + 1),
}
}
자리지정자 _ (The _ Placeholder)
match
를 사용하면서 모든 경우를 다 처리하고 싶지 않을 경우가 있을 수 있습니다. 이때 사용 가능한 패턴도 제공합니다.
아래의 예제에서는 1, 3, 5, 7 이 아닌 0~255 범위의 수에 해당하는 패턴은 _
자리지정자를 사용하여 대체하고 있습니다.
_ 패턴은 모든 값에 일치함을 의미합니다. 그래서 match
표현식의 가장 마지막에 추가하면 앞에 나열했던 패턴에 일치하지 않는 나머지 모든 패턴에 일치하게 됩니다. 여기서 ()
는 단순한 유닛값이기 때문에 아무런 일도 일어나지 않습니다.
let some_u8_value: u8 = 0u8;
match some_u8_value {
1 => println!("one"),
3 => println!("three"),
5 => println!("five"),
7 => println!("seven"),
_ => (),
}
if let
match
의 단점은 위의 Option
의 경우와 같이 단순히 한 가지의 경우만 처리해야 할 때에는 장황하고 코드가 길어진다는 단점이 있습니다.
let some_u8_value = Some(0u8);
match some_u8_value {
Some(3) => println!("three"),
_ => (),
}
아래의 코드는 위의 match
예제와 동일한 동작을 수행하는 코드입니다. Some(3)
의 경우만을 처리하고 그 외의 나머지의 값이나 None
의 경우는 처리하지 않습니다.
if let Some(3) = some_u8_value {
println!("three");
}
if let
문법은 하나의 패턴과 표현식을 등호 기호를 통해 조합합니다. if let 문법은 하나의 경우에만 처리하는 문법적 편의를 제공하는 장치로 생각하면 됩니다. 한편 if let
구문은 else
구문 또한 포함이 가능합니다. 이는 _
자리지정자와 같은 역할을 합니다.
let mut count = 0;
match coin {
Coin::Quarter(state) => println!("State quarter from {:?}!", state),
_ => count += 1,
}
let mut count = 0;
if let Coin::Quarter(state) = coin {
println!("State quarter from {:?}!", state);
} else {
count += 1;
}