소개
**Pattern Syntax (패턴 구문)**에 오신 것을 환영합니다. 이 랩은 Rust Book의 일부입니다. LabEx 에서 Rust 기술을 연습할 수 있습니다.
이 랩에서는 패턴에서 유효한 구문에 대해 논의하고 각 구문을 언제, 왜 사용해야 하는지에 대한 예시를 제공합니다.
**Pattern Syntax (패턴 구문)**에 오신 것을 환영합니다. 이 랩은 Rust Book의 일부입니다. LabEx 에서 Rust 기술을 연습할 수 있습니다.
이 랩에서는 패턴에서 유효한 구문에 대해 논의하고 각 구문을 언제, 왜 사용해야 하는지에 대한 예시를 제공합니다.
이 섹션에서는 패턴에서 유효한 모든 구문을 모으고 각 구문을 언제, 왜 사용해야 하는지에 대해 논의합니다.
6 장에서 보았듯이, 리터럴에 대해 패턴을 직접 매칭할 수 있습니다. 다음 코드는 몇 가지 예시를 제공합니다.
파일 이름: src/main.rs
let x = 1;
match x {
1 => println!("one"),
2 => println!("two"),
3 => println!("three"),
_ => println!("anything"),
}
이 코드는 one을 출력합니다. 왜냐하면 x의 값이 1이기 때문입니다. 이 구문은 특정 구체적인 값을 얻을 경우 코드가 작업을 수행하도록 하려는 경우에 유용합니다.
명명된 변수는 모든 값과 일치하는 반박할 수 없는 패턴이며, 이 책에서 여러 번 사용했습니다. 그러나 match 표현식에서 명명된 변수를 사용할 때 복잡한 문제가 발생합니다. match는 새로운 범위를 시작하기 때문에, match 표현식 내부의 패턴의 일부로 선언된 변수는 모든 변수와 마찬가지로 match 구조 외부에서 동일한 이름을 가진 변수를 가립니다. Listing 18-11 에서 Some(5) 값을 가진 x라는 변수와 10 값을 가진 y라는 변수를 선언합니다. 그런 다음 x 값에 대한 match 표현식을 생성합니다. match arm 의 패턴과 마지막 println!을 살펴보고 이 코드를 실행하거나 더 읽기 전에 코드가 무엇을 출력할지 생각해 보십시오.
파일 이름: src/main.rs
fn main() {
1 let x = Some(5);
2 let y = 10;
match x {
3 Some(50) => println!("Got 50"),
4 Some(y) => println!("Matched, y = {y}"),
5 _ => println!("Default case, x = {:?}", x),
}
6 println!("at the end: x = {:?}, y = {y}", x);
}
Listing 18-11: 그림자 변수 y를 도입하는 arm 이 있는 match 표현식
match 표현식이 실행될 때 어떤 일이 발생하는지 살펴보겠습니다. 첫 번째 match arm [3]의 패턴은 정의된 x 값 [1]과 일치하지 않으므로 코드는 계속 진행됩니다.
두 번째 match arm [4]의 패턴은 Some 값 내부의 모든 값과 일치하는 y라는 새로운 변수를 도입합니다. match 표현식 내부의 새로운 범위에 있으므로, 이것은 처음 [2]에 10 값을 가진 y가 아닌 새로운 y 변수입니다. 이 새로운 y 바인딩은 Some 내부의 모든 값과 일치하며, 이는 x에 있는 값입니다. 따라서 이 새로운 y는 x의 Some 내부 값에 바인딩됩니다. 그 값은 5이므로 해당 arm 에 대한 표현식이 실행되고 Matched, y = 5를 출력합니다.
x가 Some(5) 대신 None 값이었다면, 처음 두 arm 의 패턴은 일치하지 않았을 것이므로 값은 밑줄 [5]과 일치했을 것입니다. 밑줄 arm 의 패턴에서 x 변수를 도입하지 않았으므로 표현식의 x는 여전히 가려지지 않은 외부 x입니다. 이 가상적인 경우, match는 Default case, x = None을 출력합니다.
match 표현식이 완료되면 해당 범위가 종료되고 내부 y의 범위도 종료됩니다. 마지막 println! [6]은 at the end: x = Some(5), y = 10을 생성합니다.
그림자 변수를 도입하는 대신 외부 x와 y의 값을 비교하는 match 표현식을 만들려면 대신 match guard 조건문을 사용해야 합니다. "Match Guard 를 사용한 추가 조건문"에서 match guard 에 대해 이야기하겠습니다.
match 표현식에서 패턴 or 연산자인 | 구문을 사용하여 여러 패턴을 매칭할 수 있습니다. 예를 들어, 다음 코드에서 x의 값을 match arm 과 비교하는데, 첫 번째 arm 은 or 옵션을 가지고 있습니다. 즉, x의 값이 해당 arm 의 값 중 하나와 일치하면 해당 arm 의 코드가 실행됩니다.
파일 이름: src/main.rs
let x = 1;
match x {
1 | 2 => println!("one or two"),
3 => println!("three"),
_ => println!("anything"),
}
이 코드는 one or two를 출력합니다.
..=를 사용한 값 범위 매칭 (Matching Ranges of Values with ..=)..= 구문을 사용하면 포함 범위 (inclusive range) 의 값과 매칭할 수 있습니다. 다음 코드에서 패턴이 주어진 범위 내의 값 중 하나와 일치하면 해당 arm 이 실행됩니다.
파일 이름: src/main.rs
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 에서 1,000 사이의 모든 숫자를 매칭하려는 경우 훨씬 더 짧습니다!
컴파일러는 컴파일 시간에 범위가 비어 있지 않은지 확인하며, Rust 가 범위가 비어 있는지 여부를 알 수 있는 유일한 유형은 char 및 숫자 값이므로 범위는 숫자 또는 char 값에만 허용됩니다.
다음은 char 값의 범위를 사용하는 예입니다.
파일 이름: src/main.rs
let x = 'c';
match x {
'a'..='j' => println!("early ASCII letter"),
'k'..='z' => println!("late ASCII letter"),
_ => println!("something else"),
}
Rust 는 'c'가 첫 번째 패턴의 범위 내에 있음을 알고 early ASCII letter를 출력합니다.
또한 패턴을 사용하여 구조체 (struct), 열거형 (enum), 튜플 (tuple) 을 디스트럭처링하여 이러한 값의 다른 부분을 사용할 수 있습니다. 각 값을 살펴보겠습니다.
Listing 18-12 는 x와 y 두 개의 필드를 가진 Point 구조체를 보여주며, let 문을 사용하여 패턴으로 분해할 수 있습니다.
파일 이름: src/main.rs
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);
}
Listing 18-12: 구조체의 필드를 별도의 변수로 디스트럭처링
이 코드는 p 구조체의 x 및 y 필드의 값과 일치하는 변수 a와 b를 생성합니다. 이 예제는 패턴의 변수 이름이 구조체의 필드 이름과 일치할 필요가 없음을 보여줍니다. 그러나 변수 이름을 필드 이름과 일치시켜 어떤 변수가 어떤 필드에서 왔는지 기억하기 쉽게 만드는 것이 일반적입니다. 이러한 일반적인 사용법 때문에, 그리고 let Point { x: x, y: y } = p;를 작성하는 것은 중복이 많기 때문에, Rust 는 구조체 필드와 일치하는 패턴에 대한 단축 구문을 가지고 있습니다. 구조체 필드의 이름만 나열하면 패턴에서 생성된 변수는 동일한 이름을 갖게 됩니다. Listing 18-13 은 Listing 18-12 의 코드와 동일하게 동작하지만, let 패턴에서 생성된 변수는 a와 b 대신 x와 y입니다.
파일 이름: src/main.rs
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);
}
Listing 18-13: 구조체 필드 단축 구문을 사용하여 구조체 필드 디스트럭처링
이 코드는 p 변수의 x 및 y 필드와 일치하는 변수 x와 y를 생성합니다. 결과적으로 변수 x와 y는 p 구조체의 값을 포함합니다.
또한 모든 필드에 대한 변수를 생성하는 대신 구조체 패턴의 일부로 리터럴 값으로 디스트럭처링할 수 있습니다. 이렇게 하면 다른 필드를 디스트럭처링하기 위해 변수를 생성하면서 특정 값에 대해 일부 필드를 테스트할 수 있습니다.
Listing 18-14 에서 Point 값을 세 가지 경우로 분리하는 match 표현식이 있습니다: x 축에 직접 놓인 점 (이는 y = 0일 때 참), y 축 (x = 0) 에 놓인 점, 또는 어느 축에도 없는 점.
파일 이름: src/main.rs
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})");
}
}
}
Listing 18-14: 하나의 패턴에서 리터럴 값 디스트럭처링 및 매칭
첫 번째 arm 은 y 필드가 리터럴 0과 일치하는 경우 y 필드가 일치하도록 지정하여 x 축에 놓인 모든 점과 일치합니다. 패턴은 여전히 이 arm 에 대한 코드에서 사용할 수 있는 x 변수를 생성합니다.
마찬가지로, 두 번째 arm 은 x 필드가 0이고 y 필드의 값에 대한 변수 y를 생성하여 y 축에 있는 모든 점과 일치합니다. 세 번째 arm 은 리터럴을 지정하지 않으므로 다른 모든 Point와 일치하고 x 및 y 필드 모두에 대한 변수를 생성합니다.
이 예에서 값 p는 x가 0을 포함하기 때문에 두 번째 arm 과 일치하므로 이 코드는 On the y axis at 7을 출력합니다.
match 표현식은 첫 번째 일치하는 패턴을 찾으면 arm 확인을 중지하므로, Point { x: 0, y: 0}이 x 축과 y 축에 있더라도 이 코드는 On the x axis at 0만 출력합니다.
이 책에서 열거형을 디스트럭처링했습니다 (예: Listing 6-5). 하지만 열거형을 디스트럭처링하는 패턴이 열거형 내부에 저장된 데이터가 정의된 방식에 해당한다는 것을 아직 명시적으로 논의하지 않았습니다. 예를 들어, Listing 18-15 에서 Listing 6-2 의 Message 열거형을 사용하고 각 내부 값을 디스트럭처링하는 패턴으로 match를 작성합니다.
파일 이름: src/main.rs
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}
fn main() {
1 let msg = Message::ChangeColor(0, 160, 255);
match msg {
2 Message::Quit => {
println!(
"The Quit variant has no data to destructure."
);
}
3 Message::Move { x, y } => {
println!(
"Move in the x dir {x}, in the y dir {y}"
);
}
4 Message::Write(text) => {
println!("Text message: {text}");
}
5 Message::ChangeColor(r, g, b) => println!(
"Change color to red {r}, green {g}, and blue {b}"
),
}
}
Listing 18-15: 서로 다른 종류의 값을 저장하는 열거형 변형 디스트럭처링
이 코드는 Change color to red 0, green 160, and blue 255를 출력합니다. msg [1]의 값을 변경하여 다른 arm 의 코드가 실행되는 것을 확인해 보세요.
Message::Quit [2]와 같이 데이터가 없는 열거형 변형의 경우 값을 더 이상 디스트럭처링할 수 없습니다. 리터럴 Message::Quit 값만 일치시킬 수 있으며 해당 패턴에는 변수가 없습니다.
Message::Move [3]와 같은 구조체와 유사한 열거형 변형의 경우 구조체와 일치하도록 지정하는 패턴과 유사한 패턴을 사용할 수 있습니다. 변형 이름 뒤에 중괄호를 넣고 필드를 변수와 함께 나열하여 이 arm 에 대한 코드에서 사용할 조각을 분해합니다. 여기서는 Listing 18-13 에서 했던 것처럼 단축 형식을 사용합니다.
하나의 요소가 있는 튜플을 포함하는 Message::Write [4] 및 세 개의 요소를 포함하는 튜플을 포함하는 Message::ChangeColor [5]와 같은 튜플과 유사한 열거형 변형의 경우 패턴은 튜플과 일치하도록 지정하는 패턴과 유사합니다. 패턴의 변수 수는 일치하는 변형의 요소 수와 일치해야 합니다.
지금까지의 예제는 모두 구조체 또는 열거형을 한 단계 깊이로 매칭했지만, 매칭은 중첩된 항목에서도 작동할 수 있습니다! 예를 들어, Listing 18-15 의 코드를 리팩터링하여 Listing 18-16 과 같이 ChangeColor 메시지에서 RGB 및 HSV 색상을 지원할 수 있습니다.
파일 이름: src/main.rs
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}"
),
_ => (),
}
}
Listing 18-16: 중첩된 열거형 매칭
match 표현식의 첫 번째 arm 의 패턴은 Color::Rgb 변형을 포함하는 Message::ChangeColor 열거형 변형과 일치합니다. 그런 다음 패턴은 세 개의 내부 i32 값에 바인딩됩니다. 두 번째 arm 의 패턴도 Message::ChangeColor 열거형 변형과 일치하지만 내부 열거형은 대신 Color::Hsv와 일치합니다. 두 개의 열거형이 관련되어 있더라도 이러한 복잡한 조건을 하나의 match 표현식으로 지정할 수 있습니다.
우리는 디스트럭처링 패턴을 훨씬 더 복잡한 방식으로 혼합, 매칭 및 중첩할 수 있습니다. 다음 예제는 튜플 내부에 구조체와 튜플을 중첩하고 모든 기본 값을 디스트럭처링하는 복잡한 디스트럭처를 보여줍니다.
let ((feet, inches), Point { x, y }) =
((3, 10), Point { x: 3, y: -10 });
이 코드를 사용하면 복잡한 유형을 구성 요소로 분해하여 관심 있는 값을 개별적으로 사용할 수 있습니다.
패턴을 사용한 디스트럭처링은 구조체의 각 필드의 값과 같이 값의 조각을 서로 분리하여 사용하는 편리한 방법입니다.
match의 마지막 arm 에서와 같이 패턴에서 값을 무시하는 것이 때때로 유용하다는 것을 보았습니다. 이는 실제로 아무것도 하지 않지만 나머지 모든 가능한 값을 고려하는 catchall 을 얻기 위함입니다. 패턴에서 전체 값 또는 값의 일부를 무시하는 몇 가지 방법이 있습니다: _ 패턴 사용 (이미 보셨을 것입니다), 다른 패턴 내에서 _ 패턴 사용, 밑줄로 시작하는 이름 사용 또는 ..을 사용하여 값의 나머지 부분을 무시하는 것입니다. 이러한 각 패턴을 사용하는 방법과 이유를 살펴보겠습니다.
우리는 밑줄을 모든 값과 일치하지만 값에 바인딩되지 않는 와일드카드 패턴으로 사용했습니다. 이는 match 표현식의 마지막 arm 에서 특히 유용하지만, Listing 18-17 에 표시된 것처럼 함수 매개변수를 포함한 모든 패턴에서 사용할 수도 있습니다.
파일 이름: src/main.rs
fn foo(_: i32, y: i32) {
println!("This code only uses the y parameter: {y}");
}
fn main() {
foo(3, 4);
}
Listing 18-17: 함수 시그니처에서 _ 사용하기
이 코드는 첫 번째 인수로 전달된 값 3을 완전히 무시하고 This code only uses the y parameter: 4를 출력합니다.
대부분의 경우 특정 함수 매개변수가 더 이상 필요하지 않으면 사용하지 않는 매개변수를 포함하지 않도록 시그니처를 변경합니다. 함수 매개변수를 무시하는 것은, 예를 들어 특정 타입 시그니처가 필요하지만 구현의 함수 본문이 매개변수 중 하나가 필요하지 않은 트레이트를 구현하는 경우에 특히 유용할 수 있습니다. 그러면 이름을 사용하는 경우와 같이 사용하지 않는 함수 매개변수에 대한 컴파일러 경고를 피할 수 있습니다.
또한 다른 패턴 내에서 _를 사용하여 값의 일부만 무시할 수 있습니다. 예를 들어, 값의 일부만 테스트하고 싶지만 실행하려는 해당 코드에서 다른 부분은 사용할 필요가 없는 경우입니다. Listing 18-18 은 설정 값을 관리하는 코드를 보여줍니다. 비즈니스 요구 사항은 사용자가 설정의 기존 사용자 지정을 덮어쓸 수 없지만, 설정이 현재 설정되지 않은 경우 설정을 해제하고 값을 지정할 수 있어야 한다는 것입니다.
파일 이름: src/main.rs
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);
Listing 18-18: Some variant 내의 값을 사용할 필요가 없을 때 Some variant 와 일치하는 패턴 내에서 밑줄 사용하기
이 코드는 Can't overwrite an existing customized value를 출력한 다음 setting is Some(5)를 출력합니다. 첫 번째 match arm 에서 Some variant 내의 값을 일치시키거나 사용할 필요는 없지만, setting_value와 new_setting_value가 모두 Some variant 인 경우를 테스트해야 합니다. 이 경우 setting_value를 변경하지 않는 이유를 출력하고, 변경되지 않습니다.
두 번째 arm 의 _ 패턴으로 표현된 다른 모든 경우 ( setting_value 또는 new_setting_value가 None인 경우) 에는 new_setting_value가 setting_value가 되도록 허용합니다.
또한 하나의 패턴 내에서 여러 위치에 밑줄을 사용하여 특정 값을 무시할 수 있습니다. Listing 18-19 는 5 개의 항목으로 구성된 튜플에서 두 번째 및 네 번째 값을 무시하는 예제를 보여줍니다.
파일 이름: src/main.rs
let numbers = (2, 4, 8, 16, 32);
match numbers {
(first, _, third, _, fifth) => {
println!("Some numbers: {first}, {third}, {fifth}");
}
}
Listing 18-19: 튜플의 여러 부분 무시하기
이 코드는 Some numbers: 2, 8, 32를 출력하고, 값 4와 16은 무시됩니다.
변수를 생성했지만 어디에서도 사용하지 않으면 Rust 는 일반적으로 경고를 발행합니다. 사용하지 않는 변수는 버그일 수 있기 때문입니다. 그러나 프로토타입을 만들거나 프로젝트를 시작하는 경우와 같이 아직 사용하지 않을 변수를 생성할 수 있는 것이 유용할 때가 있습니다. 이러한 상황에서는 변수 이름을 밑줄로 시작하여 사용하지 않는 변수에 대한 경고를 받지 않도록 Rust 에 지시할 수 있습니다. Listing 18-20 에서 두 개의 사용하지 않는 변수를 생성하지만 이 코드를 컴파일하면 그 중 하나에 대해서만 경고를 받게 됩니다.
파일 이름: src/main.rs
fn main() {
let _x = 5;
let y = 10;
}
Listing 18-20: 사용하지 않는 변수 경고를 피하기 위해 변수 이름을 밑줄로 시작하기
여기서는 변수 y를 사용하지 않는다는 경고를 받지만, _x를 사용하지 않는다는 경고는 받지 않습니다.
_만 사용하는 것과 밑줄로 시작하는 이름을 사용하는 것 사이에는 미묘한 차이가 있습니다. 구문 _x는 여전히 값을 변수에 바인딩하는 반면, _는 전혀 바인딩하지 않습니다. 이러한 구분이 중요한 경우를 보여주기 위해 Listing 18-21 은 오류를 제공합니다.
파일 이름: src/main.rs
let s = Some(String::from("Hello!"));
if let Some(_s) = s {
println!("found a string");
}
println!("{:?}", s);
Listing 18-21: 밑줄로 시작하는 사용하지 않는 변수는 여전히 값을 바인딩하며, 이는 값의 소유권을 가질 수 있습니다.
s 값이 여전히 _s로 이동하여 s를 다시 사용할 수 없으므로 오류가 발생합니다. 그러나 밑줄 자체를 사용하면 값에 바인딩되지 않습니다. Listing 18-22 는 s가 _로 이동하지 않으므로 오류 없이 컴파일됩니다.
파일 이름: src/main.rs
let s = Some(String::from("Hello!"));
if let Some(_) = s {
println!("found a string");
}
println!("{:?}", s);
Listing 18-22: 밑줄을 사용하면 값을 바인딩하지 않습니다.
이 코드는 s를 아무것에도 바인딩하지 않으므로 제대로 작동합니다. 즉, 이동되지 않습니다.
여러 부분으로 구성된 값을 사용할 때, .. 구문을 사용하여 특정 부분을 사용하고 나머지를 무시하여, 무시된 각 값에 대해 밑줄을 나열할 필요가 없도록 할 수 있습니다. .. 패턴은 패턴의 나머지 부분에서 명시적으로 일치시키지 않은 값의 모든 부분을 무시합니다. Listing 18-23 에서 3 차원 공간의 좌표를 저장하는 Point 구조체가 있습니다. match 표현식에서 x 좌표만 사용하고 y 및 z 필드의 값은 무시하려고 합니다.
파일 이름: src/main.rs
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}"),
}
Listing 18-23: ..를 사용하여 x를 제외한 Point의 모든 필드 무시하기
x 값을 나열한 다음 .. 패턴을 포함합니다. 이는 특히 하나 또는 두 개의 필드만 관련이 있는 많은 필드가 있는 구조체로 작업할 때 y: _ 및 z: _를 나열하는 것보다 빠릅니다.
구문 ..는 필요한 만큼 많은 값으로 확장됩니다. Listing 18-24 는 튜플과 함께 ..를 사용하는 방법을 보여줍니다.
파일 이름: src/main.rs
fn main() {
let numbers = (2, 4, 8, 16, 32);
match numbers {
(first, .., last) => {
println!("Some numbers: {first}, {last}");
}
}
}
Listing 18-24: 튜플에서 첫 번째 및 마지막 값만 일치시키고 다른 모든 값 무시하기
이 코드에서 첫 번째 및 마지막 값은 first 및 last와 일치합니다. ..는 중간의 모든 것을 일치시키고 무시합니다.
그러나 ..를 사용하는 것은 모호하지 않아야 합니다. 어떤 값을 일치시키고 어떤 값을 무시해야 하는지 명확하지 않은 경우 Rust 는 오류를 발생시킵니다. Listing 18-25 는 ..를 모호하게 사용하는 예제를 보여주므로 컴파일되지 않습니다.
파일 이름: src/main.rs
fn main() {
let numbers = (2, 4, 8, 16, 32);
match numbers {
(.., second, ..) => {
println!("Some numbers: {second}");
},
}
}
Listing 18-25: 모호한 방식으로 ..를 사용하려는 시도
이 예제를 컴파일하면 다음과 같은 오류가 발생합니다.
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
Rust 가 second와 값을 일치시키기 전에 튜플에서 얼마나 많은 값을 무시해야 하는지, 그리고 그 후에 얼마나 많은 값을 더 무시해야 하는지 결정하는 것은 불가능합니다. 이 코드는 2를 무시하고, second를 4에 바인딩한 다음 8, 16, 32를 무시하려는 것을 의미할 수 있습니다. 또는 2와 4를 무시하고, second를 8에 바인딩한 다음 16과 32를 무시하려는 것을 의미할 수 있습니다. 등등. 변수 이름 second는 Rust 에 특별한 의미가 없으므로, 이처럼 두 곳에서 ..를 사용하는 것은 모호하기 때문에 컴파일러 오류가 발생합니다.
match guard는 match arm 에서 패턴 뒤에 지정된 추가적인 if 조건으로, 해당 arm 이 선택되려면 일치해야 합니다. Match guard 는 패턴만으로는 표현할 수 없는 더 복잡한 아이디어를 표현하는 데 유용합니다.
조건은 패턴에서 생성된 변수를 사용할 수 있습니다. Listing 18-26 은 첫 번째 arm 에 Some(x) 패턴이 있고 if x % 2 == 0의 match guard 가 있는 match를 보여줍니다 (숫자가 짝수이면 true가 됩니다).
파일 이름: src/main.rs
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 => (),
}
Listing 18-26: 패턴에 match guard 추가하기
이 예제는 The number 4 is even을 출력합니다. num이 첫 번째 arm 의 패턴과 비교될 때, Some(4)가 Some(x)와 일치하기 때문에 일치합니다. 그런 다음 match guard 는 x를 2 로 나눈 나머지가 0 과 같은지 확인하고, 그렇기 때문에 첫 번째 arm 이 선택됩니다.
num이 대신 Some(5)였다면, 첫 번째 arm 의 match guard 는 5 를 2 로 나눈 나머지가 1 이고 0 과 같지 않기 때문에 false였을 것입니다. 그러면 Rust 는 두 번째 arm 으로 이동하며, 두 번째 arm 에는 match guard 가 없으므로 모든 Some 변형과 일치하기 때문에 일치합니다.
패턴 내에서 if x % 2 == 0 조건을 표현할 방법이 없으므로 match guard 는 이 로직을 표현할 수 있는 기능을 제공합니다. 이 추가적인 표현력의 단점은 match guard 표현식이 포함될 때 컴파일러가 완전성을 확인하려고 시도하지 않는다는 것입니다.
Listing 18-11 에서 패턴 - 섀도잉 문제를 해결하기 위해 match guard 를 사용할 수 있다고 언급했습니다. match 외부의 변수를 사용하는 대신 match 표현식의 패턴 내에서 새 변수를 생성했음을 기억하십시오. 그 새 변수는 외부 변수의 값에 대해 테스트할 수 없다는 것을 의미했습니다. Listing 18-27 은 이 문제를 해결하기 위해 match guard 를 사용하는 방법을 보여줍니다.
파일 이름: src/main.rs
fn main() {
let x = Some(5);
let y = 10;
match x {
Some(50) => println!("Got 50"),
Some(n) if n == y => println!("Matched, n = {n}"),
_ => println!("Default case, x = {:?}", x),
}
println!("at the end: x = {:?}, y = {y}", x);
}
Listing 18-27: 외부 변수와 동일성을 테스트하기 위해 match guard 사용하기
이 코드는 이제 Default case, x = Some(5)를 출력합니다. 두 번째 match arm 의 패턴은 외부 y를 섀도잉할 새 변수 y를 도입하지 않으므로, match guard 에서 외부 y를 사용할 수 있습니다. 외부 y를 섀도잉했을 Some(y)로 패턴을 지정하는 대신, Some(n)을 지정합니다. 이렇게 하면 match 외부에는 n 변수가 없으므로 아무것도 섀도잉하지 않는 새 변수 n이 생성됩니다.
match guard if n == y는 패턴이 아니므로 새 변수를 도입하지 않습니다. 이 y는 새 섀도잉된 y가 아닌 외부 y이며, n을 y와 비교하여 외부 y와 동일한 값을 갖는 값을 찾을 수 있습니다.
or 연산자 |를 match guard 에서 사용하여 여러 패턴을 지정할 수도 있습니다. match guard 조건은 모든 패턴에 적용됩니다. Listing 18-28 은 |를 사용하는 패턴과 match guard 를 결합할 때의 우선 순위를 보여줍니다. 이 예제의 중요한 부분은 if y match guard 가 4, 5, and 6에 적용된다는 것입니다. if y가 6에만 적용되는 것처럼 보일 수 있지만 말입니다.
파일 이름: src/main.rs
let x = 4;
let y = false;
match x {
4 | 5 | 6 if y => println!("yes"),
_ => println!("no"),
}
Listing 18-28: match guard 와 여러 패턴 결합하기
match 조건은 x의 값이 4, 5, 또는 6과 같고 and y가 true인 경우에만 arm 이 일치한다고 명시합니다. 이 코드가 실행될 때, 첫 번째 arm 의 패턴은 x가 4이기 때문에 일치하지만, match guard if y는 false이므로 첫 번째 arm 이 선택되지 않습니다. 코드는 두 번째 arm 으로 이동하며, 이는 일치하고 이 프로그램은 no를 출력합니다. 그 이유는 if 조건이 패턴 전체 4 | 5 | 6에 적용되기 때문이며, 마지막 값 6에만 적용되는 것이 아닙니다. 즉, match guard 의 우선 순위는 패턴과 관련하여 다음과 같이 동작합니다.
(4 | 5 | 6) if y => ...
이것이 아니라:
4 | 5 | (6 if y) => ...
코드를 실행한 후, 우선 순위 동작이 분명해집니다. match guard 가 | 연산자를 사용하여 지정된 값 목록의 마지막 값에만 적용되었다면, arm 이 일치하고 프로그램은 yes를 출력했을 것입니다.
at 연산자 @를 사용하면 패턴 일치를 위해 값을 테스트하는 동시에 값을 보유하는 변수를 생성할 수 있습니다. Listing 18-29 에서 Message::Hello id 필드가 범위 3..=7 내에 있는지 테스트하려고 합니다. 또한 값을 id_variable 변수에 바인딩하여 arm 과 관련된 코드에서 사용할 수 있도록 하려고 합니다. 이 변수의 이름을 필드와 동일하게 id로 지정할 수도 있지만, 이 예제에서는 다른 이름을 사용합니다.
파일 이름: src/main.rs
enum Message {
Hello { id: i32 },
}
let msg = Message::Hello { id: 5 };
match msg {
Message::Hello {
id: id_variable @ 3..=7,
} => println!("Found an id in range: {id_variable}"),
Message::Hello { id: 10..=12 } => {
println!("Found an id in another range")
}
Message::Hello { id } => println!("Some other id: {id}"),
}
Listing 18-29: @를 사용하여 패턴에서 값을 테스트하는 동시에 바인딩하기
이 예제는 Found an id in range: 5를 출력합니다. 범위 3..=7 앞에 id_variable @를 지정하여, 범위 패턴과 일치하는 값을 테스트하는 동시에 범위와 일치하는 모든 값을 캡처합니다.
두 번째 arm 에서는 패턴에 범위만 지정되어 있으며, arm 과 관련된 코드에는 id 필드의 실제 값을 포함하는 변수가 없습니다. id 필드의 값은 10, 11 또는 12 일 수 있지만, 해당 패턴과 함께 제공되는 코드는 어떤 값인지 알지 못합니다. id 값을 변수에 저장하지 않았기 때문에 패턴 코드는 id 필드의 값을 사용할 수 없습니다.
마지막 arm 에서는 범위를 지정하지 않고 변수를 지정했으므로, id라는 변수에서 arm 의 코드에서 사용할 수 있는 값이 있습니다. 그 이유는 구조체 필드 축약 구문을 사용했기 때문입니다. 그러나 이 arm 에서는 처음 두 arm 에서와 같이 id 필드의 값에 대한 테스트를 적용하지 않았습니다. 모든 값이 이 패턴과 일치합니다.
@를 사용하면 값을 테스트하고 하나의 패턴 내에서 변수에 저장할 수 있습니다.
축하합니다! 패턴 구문 (Pattern Syntax) 랩을 완료했습니다. LabEx 에서 더 많은 랩을 연습하여 실력을 향상시킬 수 있습니다.