패턴 문법
이 절에서는 패턴 안에서 사용할 수 있는 문법을 전부 모아 보고, 각각을 왜 언제 쓰고 싶은지 설명합니다.
리터럴 매칭
6장에서 보았듯이, 패턴은 리터럴 값과 직접 매칭될 수 있습니다. 다음 코드는 몇 가지 예를 보여 줍니다.
fn main() {
let x = 1;
match x {
1 => println!("one"),
2 => println!("two"),
3 => println!("three"),
_ => println!("anything"),
}
}
이 코드는 x 값이 1 이기 때문에 one 을 출력합니다. 이런 문법은, 어떤 값이
특정한 구체 값일 때만 코드를 실행하고 싶을 때 유용합니다.
이름 있는 변수 매칭
이름 있는 변수는 어떤 값과도 매칭되는 반박 불가능한 패턴이며, 이 책 곳곳에서 많이
사용해 왔습니다. 하지만 match, if let, while let 식 안에서 이름 있는 변수를
사용할 때는 주의할 점이 있습니다. 이런 식들은 새 스코프를 만들기 때문에, 그 안의
패턴으로 선언한 변수는 바깥쪽에 같은 이름이 있더라도 그것을 가리게 됩니다. 이는
일반 변수와 똑같은 규칙입니다. 목록 19-11에서는 값이 Some(5) 인 x 와, 값이
10 인 y 를 선언한 뒤, x 에 대해 match 를 실행합니다. 실행하기 전에
각 arm의 패턴과 마지막 println! 을 보고 이 코드가 무엇을 출력할지 먼저 생각해
보세요.
fn main() {
let x = Some(5);
let y = 10;
match x {
Some(50) => println!("Got 50"),
Some(y) => println!("Matched, y = {y}"),
_ => println!("Default case, x = {x:?}"),
}
println!("at the end: x = {x:?}, y = {y}");
}
y 를 가리는 새 변수 하나를 도입하는 arm을 가진 match 식이 match 식이 실행될 때 어떤 일이 일어나는지 하나씩 봅시다. 첫 번째 arm의 패턴은
x 의 실제 값과 맞지 않으므로, 코드는 다음 arm으로 넘어갑니다.
두 번째 arm의 패턴은 Some 안 어떤 값이든 받아들이는 새 변수 y 를 도입합니다.
이 변수는 match 식 안의 새로운 스코프에 있기 때문에, 처음에 값 10 으로 선언했던
바깥의 y 와는 다른 새 변수입니다. 이 새 y 바인딩은 Some 안의 어떤 값과도
매칭되며, 우리가 가진 x 값이 바로 그렇습니다. 따라서 이 새 y 는 x 안
Some 의 내부 값인 5 에 바인딩됩니다. 그래서 이 arm의 식이 실행되어
Matched, y = 5 를 출력합니다.
만약 x 가 Some(5) 가 아니라 None 이었다면, 앞의 두 arm 모두 매칭되지 않고
마지막 _ arm으로 갔을 것입니다. _ arm 안의 표현에서 x 를 사용할 때는, 그
패턴에서 새 변수를 만들지 않았으므로 여전히 바깥쪽 x 를 의미합니다. 이런 가상의
상황이라면 match 는 Default case, x = None 을 출력했을 것입니다.
match 식이 끝나면, 그 스코프도 끝나고 안쪽 y 도 사라집니다. 마지막 println!
은 따라서 at the end: x = Some(5), y = 10 을 출력합니다.
바깥쪽 x 와 y 값을 서로 비교하는 match 식을 만들고 싶다면, 기존 y 를 가리는
새 변수를 만들면 안 됩니다. 대신 매치 가드(match guard) 같은 조건을 써야 합니다.
이는 뒤의 “매치 가드로 조건 추가하기”
절에서 다룹니다.
여러 패턴 매칭하기
match 식에서는 | 문법을 사용해 여러 패턴을 한 arm 안에 쓸 수 있습니다.
이 | 는 패턴의 또는(or) 연산자입니다. 예를 들어 다음 코드에서는 x 값을
각 arm과 비교하는데, 첫 번째 arm은 | 를 사용해 값이 둘 중 어느 것이든 맞으면
해당 arm의 코드가 실행되도록 합니다.
fn main() {
let x = 1;
match x {
1 | 2 => println!("one or two"),
3 => println!("three"),
_ => println!("anything"),
}
}
이 코드는 one or two 를 출력합니다.
..= 로 값 범위 매칭하기
..= 문법은 값을 포함하는 범위와 매칭할 수 있게 해 줍니다. 다음 코드에서는 패턴이
주어진 범위 안의 어떤 값과 맞기만 하면, 그 arm이 실행됩니다.
fn main() {
let x = 5;
match x {
1..=5 => println!("one through five"),
_ => println!("something else"),
}
}
x 가 1, 2, 3, 4, 5 중 하나라면 첫 번째 arm이 매칭됩니다. 같은 의미를
| 로 쓰려면 1 | 2 | 3 | 4 | 5 처럼 적어야 하므로, 범위 문법이 훨씬 더 편리합니다.
특히 1부터 1000 사이 아무 수든 매칭하고 싶을 때처럼 범위가 클수록 더 그렇습니다.
컴파일러는 컴파일 시점에 이 범위가 비어 있지 않은지도 검사합니다. 다만 러스트가
“이 범위가 비었는지 아닌지”를 판별할 수 있는 타입은 char 와 숫자뿐이기 때문에,
범위 패턴은 숫자나 char 에만 사용할 수 있습니다.
다음은 char 범위를 사용하는 예입니다.
fn main() {
let x = 'c';
match x {
'a'..='j' => println!("early ASCII letter"),
'k'..='z' => println!("late ASCII letter"),
_ => println!("something else"),
}
}
러스트는 'c' 가 첫 번째 패턴의 범위 안에 있다는 것을 알아낼 수 있고,
early ASCII letter 를 출력합니다.
구조분해로 값 쪼개기
패턴은 구조체, enum, 튜플을 구조분해해서 그 안의 서로 다른 부분을 꺼내 쓸 때도 사용할 수 있습니다. 하나씩 살펴봅시다.
구조체
목록 19-12는 x, y 라는 두 필드를 가진 Point 구조체를 보여 줍니다. let
문 안의 패턴을 사용해 이 구조체를 구조분해할 수 있습니다.
struct Point {
x: i32,
y: i32,
}
fn main() {
let p = Point { x: 0, y: 7 };
let Point { x: a, y: b } = p;
assert_eq!(0, a);
assert_eq!(7, b);
}
이 코드는 구조체 p 의 x 와 y 필드 값을 각각 a, b 변수에 바인딩합니다.
여기서 중요한 점은, 패턴 안의 변수 이름이 구조체 필드 이름과 꼭 같을 필요는 없다는
것입니다. 하지만 어떤 변수가 어떤 필드에서 왔는지 기억하기 쉽게 하기 위해, 실제로는
필드 이름과 같은 이름을 쓰는 경우가 더 흔합니다. 이 패턴이 흔하기 때문에, 그리고
let Point { x: x, y: y } = p; 라고 쓰면 반복이 너무 많기 때문에, 러스트는
구조체 필드를 매칭하는 패턴에 대한 축약 문법도 제공합니다. 구조체 필드 이름만 적으면,
그 패턴으로부터 만들어지는 변수도 같은 이름을 갖게 됩니다. 목록 19-13은 목록 19-12와
같은 동작을 하지만, a, b 대신 x, y 라는 변수 이름을 사용합니다.
struct Point {
x: i32,
y: i32,
}
fn main() {
let p = Point { x: 0, y: 7 };
let Point { x, y } = p;
assert_eq!(0, x);
assert_eq!(7, y);
}
이 코드는 p 의 x, y 필드 값을 각각 같은 이름의 변수 x, y 에 바인딩합니다.
결과적으로 변수 x 와 y 안에는 p 구조체의 필드 값이 들어가게 됩니다.
또한 구조체 패턴 안에 리터럴 값을 넣어, 모든 필드에 대해 새 변수를 만드는 대신 일부 필드는 특정 값과만 매칭되도록 만들 수도 있습니다. 이를 통해 어떤 필드는 특정 값을 검사하고, 나머지 필드는 변수로 꺼내 쓸 수 있습니다.
목록 19-14에서는 match 식을 사용해 Point 값을 세 가지 경우로 나눕니다.
x 축 위에 있는 점(y = 0), y 축 위에 있는 점(x = 0), 그리고 둘 다 아닌
점입니다.
struct Point {
x: i32,
y: i32,
}
fn main() {
let p = Point { x: 0, y: 7 };
match p {
Point { x, y: 0 } => println!("On the x axis at {x}"),
Point { x: 0, y } => println!("On the y axis at {y}"),
Point { x, y } => {
println!("On neither axis: ({x}, {y})");
}
}
}
첫 번째 arm은 y 필드 값이 리터럴 0 과 맞는 모든 점을 매칭합니다. 이 경우에도
x 값은 변수로 바인딩되므로, 그 arm의 코드 안에서 사용할 수 있습니다.
마찬가지로 두 번째 arm은 x 필드 값이 0 인 모든 점과 매칭되며, y 필드 값은
변수 y 로 바인딩합니다. 세 번째 arm은 아무 리터럴도 지정하지 않으므로 다른 모든
Point 와 매칭되고, x, y 둘 다 변수로 바인딩합니다.
이 예제에서 값 p 는 x 가 0 이기 때문에 두 번째 arm과 매칭되므로,
On the y axis at 7 을 출력합니다.
match 식은 가장 먼저 맞는 패턴을 찾으면 그 즉시 멈춘다는 점을 기억하세요.
따라서 Point { x: 0, y: 0 } 은 사실 x 축 위이기도 하고 y 축 위이기도
하지만, 이 코드는 오직 On the x axis at 0 만 출력할 것입니다.
enum
이 책 곳곳에서 enum을 구조분해했지만(예를 들어 6장의 목록 6-5), enum을 구조분해하는
패턴이 실제로는 enum 내부 데이터 정의 방식과 정확히 대응한다는 사실을 명시적으로는
아직 이야기하지 않았습니다. 예시로, 목록 6-2의 Message enum을 사용해, 각 내부
값을 구조분해하는 패턴을 가진 match 를 목록 19-15에 작성해 봅시다.
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}
fn main() {
let msg = Message::ChangeColor(0, 160, 255);
match msg {
Message::Quit => {
println!("The Quit variant has no data to destructure.");
}
Message::Move { x, y } => {
println!("Move in the x direction {x} and in the y direction {y}");
}
Message::Write(text) => {
println!("Text message: {text}");
}
Message::ChangeColor(r, g, b) => {
println!("Change color to red {r}, green {g}, and blue {b}");
}
}
}
이 코드는 Change color to red 0, green 160, and blue 255 를 출력합니다.
msg 값을 바꿔가며 다른 arm 코드가 어떻게 실행되는지도 직접 확인해 보세요.
Message::Quit 처럼 데이터를 전혀 가지지 않는 enum variant는 더 이상 구조분해할
것이 없습니다. 이런 경우 우리는 단지 리터럴 값 Message::Quit 과 매칭할 수 있을
뿐이며, 그 패턴 안에 바인딩할 변수는 없습니다.
Message::Move 처럼 구조체와 비슷한 enum variant라면, 구조체와 매칭할 때 썼던 것과
비슷한 패턴을 사용할 수 있습니다. variant 이름 뒤에 중괄호를 쓰고, 그 안에 필드 이름과
변수를 써서 값을 쪼개어 arm 안 코드에서 사용할 수 있습니다. 여기서는 목록 19-13과
같은 축약 형태를 사용했습니다.
Message::Write 처럼 요소 하나를 가진 튜플 형태 variant나, Message::ChangeColor
처럼 요소 셋을 가진 variant라면, 튜플 패턴과 비슷한 문법을 사용합니다. 이 경우 패턴
안 변수 수는 매칭하려는 variant 안 요소 수와 같아야 합니다.
중첩된 구조체와 enum
지금까지 예시는 구조체나 enum을 한 단계만 구조분해했지만, 패턴은 중첩된 항목에도
당연히 사용할 수 있습니다! 예를 들어 목록 19-16은 목록 19-15 코드를 리팩터링해,
ChangeColor 메시지가 RGB와 HSV 두 가지 색 표현을 모두 지원하도록 바꾼 예입니다.
enum Color {
Rgb(i32, i32, i32),
Hsv(i32, i32, i32),
}
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(Color),
}
fn main() {
let msg = Message::ChangeColor(Color::Hsv(0, 160, 255));
match msg {
Message::ChangeColor(Color::Rgb(r, g, b)) => {
println!("Change color to red {r}, green {g}, and blue {b}");
}
Message::ChangeColor(Color::Hsv(h, s, v)) => {
println!("Change color to hue {h}, saturation {s}, value {v}");
}
_ => (),
}
}
이 match 식의 첫 번째 arm 패턴은 Color::Rgb variant를 담고 있는
Message::ChangeColor 를 매칭하고, 그 안쪽의 세 i32 값을 변수로 바인딩합니다.
두 번째 arm 역시 Message::ChangeColor 와 매칭하지만, 이번에는 내부 enum이
Color::Hsv 와 맞습니다. 이렇게 enum 두 개가 얽혀 있어도, 하나의 match
식 안에서 이런 복잡한 조건을 자연스럽게 표현할 수 있습니다.
구조체와 튜플 함께 구조분해하기
구조분해 패턴은 더 복잡하게 섞어 쓸 수도 있습니다. 다음 예제는 튜플 안에 구조체와 튜플이 다시 들어 있고, 그 안의 원시 값들까지 전부 꺼내는 복잡한 구조분해를 보여 줍니다.
fn main() {
struct Point {
x: i32,
y: i32,
}
let ((feet, inches), Point { x, y }) = ((3, 10), Point { x: 3, y: -10 });
}
이 코드는 복잡한 타입을 구성 요소들로 분해해, 우리가 관심 있는 값만 따로 사용할 수 있게 해 줍니다.
이처럼 패턴을 사용한 구조분해는, 예를 들어 구조체 각 필드 값을 서로 독립적으로 사용하고 싶을 때 아주 편리합니다.
패턴에서 값 무시하기
패턴 안에서 값을 무시하고 싶을 때가 종종 있습니다. 예를 들어 match 마지막 arm처럼,
실제로는 아무 일도 하지 않지만 “남은 가능한 모든 경우를 다 처리했다”는 의미를 위해
필요한 경우가 그렇습니다. 패턴에서 값 전체나 값 일부를 무시하는 방법은 여러 가지입니다.
이미 보았던 _ 패턴을 쓰는 방법, 다른 패턴 안에서 _ 를 쓰는 방법, 밑줄로 시작하는
이름을 쓰는 방법, 그리고 .. 을 사용해 값의 나머지 부분을 무시하는 방법이 있습니다.
이제 각각을 왜, 어떻게 쓰는지 살펴보겠습니다.
_ 로 값 전체 무시하기
우리는 _ 를 어떤 값과도 매칭되지만 그 값을 변수에 바인딩하지 않는 와일드카드 패턴으로
이미 써 보았습니다. 이 패턴은 특히 match 식의 마지막 arm에서 자주 유용하지만,
함수 매개변수처럼 패턴을 쓸 수 있는 다른 곳에서도 쓸 수 있습니다. 목록 19-17을 보세요.
fn foo(_: i32, y: i32) {
println!("This code only uses the y parameter: {y}");
}
fn main() {
foo(3, 4);
}
_ 사용하기이 코드는 첫 번째 인수로 넘긴 값 3 을 완전히 무시하고, This code only uses the y parameter: 4
를 출력합니다.
대부분의 경우, 더 이상 쓰지 않는 함수 매개변수가 있다면 시그니처 자체에서 그 매개변수를
없애는 편이 낫습니다. 하지만 예를 들어 어떤 트레이트를 구현하는데, 요구되는 시그니처
때문에 매개변수는 있어야 하나 실제 메서드 본문에서는 필요 없는 경우가 있습니다.
이럴 때 _ 를 사용하면, 사용하지 않는 매개변수에 대해 컴파일러가 경고를 내지 않게
할 수 있습니다.
중첩된 _ 로 값 일부 무시하기
어떤 값의 일부만 검사하고 나머지는 코드에서 쓰지 않을 때는, 다른 패턴 안에 _
를 넣어 그 부분만 무시할 수도 있습니다. 목록 19-18은 설정값을 관리하는 코드입니다.
비즈니스 요구사항은 이렇습니다. 사용자가 이미 커스터마이즈한 설정을 덮어쓰면 안 되지만,
현재 설정이 비어 있다면 새 값을 넣을 수는 있어야 합니다.
fn main() {
let mut setting_value = Some(5);
let new_setting_value = Some(10);
match (setting_value, new_setting_value) {
(Some(_), Some(_)) => {
println!("Can't overwrite an existing customized value");
}
_ => {
setting_value = new_setting_value;
}
}
println!("setting is {setting_value:?}");
}
Some variant 안의 실제 값은 쓰지 않을 때, 패턴 안에서 _ 를 사용하는 예이 코드는 Can't overwrite an existing customized value 를 출력한 뒤,
setting is Some(5) 를 출력합니다. 첫 번째 match arm에서는 Some 안의 값들에
대해 매칭하거나 직접 사용할 필요는 없지만, setting_value 와 new_setting_value
둘 다가 Some variant인 경우를 검사할 필요는 있습니다. 그 경우에는
setting_value 를 바꾸지 말아야 하는 이유를 출력하고, 실제 값은 그대로 둡니다.
나머지 모든 경우, 즉 setting_value 또는 new_setting_value 둘 중 하나가
None 인 경우는 두 번째 arm의 _ 패턴이 처리합니다. 이 arm에서는
new_setting_value 가 setting_value 가 되도록 허용합니다.
하나의 패턴 안에서도 _ 를 여러 번 사용해 특정 값 여러 개를 동시에 무시할 수 있습니다.
목록 19-19는 다섯 요소짜리 튜플에서 두 번째와 네 번째 값만 무시하는 예입니다.
fn main() {
let numbers = (2, 4, 8, 16, 32);
match numbers {
(first, _, third, _, fifth) => {
println!("Some numbers: {first}, {third}, {fifth}");
}
}
}
이 코드는 Some numbers: 2, 8, 32 를 출력하고, 4 와 16 은 무시합니다.
이름을 _ 로 시작해 미사용 변수 무시하기
변수를 만들었지만 어디에서도 사용하지 않으면, 러스트는 대개 “이 변수를 안 쓰는데, 버그 아니냐?”는 경고를 냅니다. 하지만 프로토타이핑을 하거나 프로젝트를 막 시작한 상태에서는, 일단 변수를 만들어 두고 아직 쓰지 않는 경우가 있습니다. 이런 상황에서는 변수 이름을 밑줄로 시작해, 사용하지 않는 변수에 대해 경고하지 말라고 러스트에게 알릴 수 있습니다. 목록 19-20에서는 사용하지 않는 변수 두 개를 만들지만, 컴파일 시 경고는 한 개에 대해서만 나옵니다.
fn main() {
let _x = 5;
let y = 10;
}
여기서는 변수 y 를 쓰지 않았다는 경고는 나오지만, _x 를 쓰지 않았다는 경고는
나오지 않습니다.
다만 _ 하나만 쓰는 것과, 밑줄로 시작하는 이름을 쓰는 것 사이에는 미묘한 차이가
있습니다. _x 는 여전히 값을 변수에 바인딩 하지만, _ 는 아예 바인딩하지
않습니다. 이 차이가 중요한 상황을 목록 19-21이 보여 줍니다.
fn main() {
let s = Some(String::from("Hello!"));
if let Some(_s) = s {
println!("found a string");
}
println!("{s:?}");
}
이 경우 s 의 값은 _s 로 이동되므로, 이후 s 를 다시 쓸 수 없다는 오류를
얻게 됩니다. 반면 _ 하나만 쓰면 값은 전혀 바인딩되지 않습니다. 그래서 목록 19-22는
오류 없이 컴파일됩니다. s 가 어디로도 이동하지 않기 때문입니다.
fn main() {
let s = Some(String::from("Hello!"));
if let Some(_) = s {
println!("found a string");
}
println!("{s:?}");
}
_ 는 값을 전혀 바인딩하지 않는다이 코드는 잘 동작합니다. 우리는 s 를 어디에도 바인딩하지 않았기 때문에, 소유권 이동도
일어나지 않았습니다.
.. 로 값의 나머지 부분 무시하기
값에 많은 부분이 있을 때는 .. 문법을 사용해 특정 부분만 쓰고 나머지는 무시할 수
있습니다. 그러면 무시할 값마다 _ 를 일일이 나열하지 않아도 됩니다. .. 패턴은
패턴에서 명시적으로 매칭하지 않은 “나머지 부분 전체”를 무시합니다. 목록 19-23에는
3차원 공간 좌표를 담는 Point 구조체가 있고, match 식에서 x 좌표만 사용하고
y, z 는 무시하려 합니다.
fn main() {
struct Point {
x: i32,
y: i32,
z: i32,
}
let origin = Point { x: 0, y: 0, z: 0 };
match origin {
Point { x, .. } => println!("x is {x}"),
}
}
.. 로 Point 의 x 만 남기고 다른 모든 필드 무시하기우리는 x 값만 명시하고, 나머지는 .. 패턴으로 처리했습니다. 특히 필드가 많은 구조체에서
하나나 두 개 필드만 중요할 때, y: _, z: _ 를 일일이 적는 것보다 훨씬 간단합니다.
.. 문법은 필요한 만큼 값들을 자동으로 확장해 줍니다. 목록 19-24는 튜플과 함께
사용하는 예입니다.
fn main() {
let numbers = (2, 4, 8, 16, 32);
match numbers {
(first, .., last) => {
println!("Some numbers: {first}, {last}");
}
}
}
이 코드에서 첫 번째와 마지막 값은 각각 first, last 와 매칭되고, .. 는
가운데의 다른 값들을 전부 무시합니다.
하지만 .. 사용은 모호해서는 안 됩니다. 어떤 값이 매칭용이고 어떤 값이 무시용인지
불분명하다면, 러스트는 컴파일 오류를 냅니다. 목록 19-25는 .. 를 모호하게 쓴
예이므로 컴파일되지 않습니다.
fn main() {
let numbers = (2, 4, 8, 16, 32);
match numbers {
(.., second, ..) => {
println!("Some numbers: {second}")
},
}
}
.. 를 모호하게 사용하려는 시도이 예제를 컴파일하면 다음과 같은 오류가 납니다.
$ cargo run
Compiling patterns v0.1.0 (file:///projects/patterns)
error: `..` can only be used once per tuple pattern
--> src/main.rs:5:22
|
5 | (.., second, ..) => {
| -- ^^ can only be used once per tuple pattern
| |
| previously used here
error: could not compile `patterns` (bin "patterns") due to 1 previous error
러스트는 이 튜플에서 second 와 매칭하기 전에 몇 개를 무시해야 하는지, 그리고 그
다음 또 몇 개를 더 무시해야 하는지 판단할 수 없습니다. 예를 들어 2 를 무시하고
second 를 4 와 매칭한 뒤 8, 16, 32 를 무시하자는 뜻일 수도 있고,
2, 4 를 무시한 뒤 second 를 8 과 매칭하고 16, 32 를 무시하자는
뜻일 수도 있습니다. second 라는 변수 이름 자체에는 러스트가 특별한 의미를 두지
않으므로, 이렇게 두 곳에 .. 를 사용하는 것은 모호하다는 컴파일 오류가 납니다.
매치 가드로 조건 추가하기
매치 가드(match guard) 는 match arm 안 패턴 뒤에 붙는 추가적인 if 조건입니다.
해당 arm이 선택되려면 이 조건까지 함께 만족해야 합니다. 매치 가드는 패턴만으로는
표현하기 어려운 더 복잡한 조건을 나타낼 때 유용합니다. 다만 이것은 match 식에서만
사용 가능하고, if let 이나 while let 에는 붙일 수 없습니다.
이 조건 안에서는 패턴으로 만든 변수도 사용할 수 있습니다. 목록 19-26은 첫 번째 arm에
Some(x) 패턴과 함께 if x % 2 == 0 이라는 매치 가드를 추가한 예입니다.
이 조건은 숫자가 짝수일 때 true 가 됩니다.
fn main() {
let num = Some(4);
match num {
Some(x) if x % 2 == 0 => println!("The number {x} is even"),
Some(x) => println!("The number {x} is odd"),
None => (),
}
}
이 예제는 The number 4 is even 을 출력합니다. num 이 첫 번째 arm 패턴과 비교될 때,
Some(4) 는 Some(x) 와 매칭됩니다. 그 다음 매치 가드는 x % 2 == 0 인지를
검사하고, 이 조건도 참이므로 첫 번째 arm이 선택됩니다.
만약 num 이 Some(5) 였다면, 첫 번째 arm의 매치 가드는 false 가 되었을
것입니다. 5를 2로 나눈 나머지는 1이고, 이는 0이 아니기 때문입니다. 그러면 러스트는
다음 arm으로 이동하고, 그 arm에는 매치 가드가 없으므로 Some variant와 매칭되었을
것입니다.
if x % 2 == 0 같은 조건은 패턴 안에 직접 표현할 방법이 없습니다. 바로 이 지점에서
매치 가드가 유용합니다.