패턴이 사용될 수 있는 모든 위치
패턴은 러스트 곳곳에 등장하며, 여러분도 모르는 사이에 이미 많이 써 왔습니다! 이 절에서는 패턴을 사용할 수 있는 모든 위치를 살펴봅니다.
match arm
6장에서 설명했듯, 우리는 match 식의 arm 안에서 패턴을 사용합니다. 형식적으로 보면
match 식은 match 키워드와, 비교할 값 하나, 그리고 “패턴 + 값이 맞을 때 실행할
식”으로 이루어진 arm 하나 이상으로 구성됩니다. 형태는 다음과 같습니다.
match VALUE {
PATTERN => EXPRESSION,
PATTERN => EXPRESSION,
PATTERN => EXPRESSION,
}
예를 들어 다음은 6장의 목록 6-5에서 변수 x 안 Option<i32> 값에 대해
매칭하던 match 식입니다.
match x {
None => None,
Some(i) => Some(i + 1),
}
이 match 식에서 패턴은 각 화살표 왼쪽의 None 과 Some(i) 입니다.
match 식에는 한 가지 요구사항이 있습니다. 반드시 완전해야 한다는 점입니다.
즉 match 안에서 다루는 값이 가질 수 있는 모든 가능성이 반드시 어떤 arm 에서든
처리되어야 합니다. 이를 만족하는 한 가지 방법은 마지막 arm에 “모든 것을 받는”
catch-all 패턴을 두는 것입니다. 예를 들어 어떤 값과도 매칭될 수 있는 변수 이름은
실패할 수 없기 때문에 나머지 모든 경우를 덮을 수 있습니다.
특히 _ 패턴은 무엇이든 매칭되지만, 그 값을 변수에 바인딩하지는 않습니다. 따라서
어떤 값을 무시하고 싶을 때 마지막 match arm에서 자주 사용합니다. 이 _ 패턴은
뒤의 [“패턴에서 값 무시하기”][ignoring-values-in-a-pattern]
절에서 더 자세히 다룹니다.
let 문
이 장 전까지 우리는 패턴을 match 와 if let 에서만 명시적으로 이야기했지만,
사실은 let 문에서도 계속 패턴을 써 왔습니다. 예를 들어 다음처럼 단순한 변수
대입을 보세요.
#![allow(unused)]
fn main() {
let x = 5;
}
이런 let 문을 쓸 때마다 사실 패턴을 사용하고 있었던 것입니다! 형식적으로 let
문은 다음과 같은 모양입니다.
let PATTERN = EXPRESSION;
let x = 5; 같은 문장에서 PATTERN 자리에 오는 변수 이름은, 사실 매우 단순한 형태의
패턴입니다. 러스트는 오른쪽 식을 왼쪽 패턴과 비교하고, 패턴 안에서 발견한 이름에 값을
바인딩합니다. 따라서 let x = 5; 에서 x 는 “여기에 맞는 값을 변수 x 에
묶어라”는 뜻의 패턴입니다. x 가 패턴 전체이므로, 사실상 “무슨 값이든 전부 x
에 바인딩하라”와 같습니다.
패턴 매칭이라는 측면을 좀 더 분명히 보기 위해, 목록 19-1처럼 let 안 패턴으로 튜플을
구조분해해 보겠습니다.
fn main() {
let (x, y, z) = (1, 2, 3);
}
여기서 러스트는 값 (1, 2, 3) 을 패턴 (x, y, z) 와 비교하고, 양쪽 요소 개수가
같다는 것을 확인한 뒤 1 을 x, 2 를 y, 3 을 z 에 바인딩합니다.
이 튜플 패턴은, 그 안에 세 개의 개별 변수 패턴이 중첩되어 있는 것으로 생각해도 됩니다.
패턴 안 요소 수가 튜플 안 요소 수와 맞지 않으면, 전체 타입이 맞지 않으므로 컴파일 오류가 납니다. 예를 들어 목록 19-2는 요소가 세 개인 튜플을 두 변수에만 구조분해하려는 잘못된 시도입니다.
fn main() {
let (x, y) = (1, 2, 3);
}
이 코드를 컴파일하려 하면 다음과 같은 타입 오류가 납니다.
$ cargo run
Compiling patterns v0.1.0 (file:///projects/patterns)
error[E0308]: mismatched types
--> src/main.rs:2:9
|
2 | let (x, y) = (1, 2, 3);
| ^^^^^^ --------- this expression has type `({integer}, {integer}, {integer})`
| |
| expected a tuple with 3 elements, found one with 2 elements
|
= note: expected tuple `({integer}, {integer}, {integer})`
found tuple `(_, _)`
For more information about this error, try `rustc --explain E0308`.
error: could not compile `patterns` (bin "patterns") due to 1 previous error
이 오류를 고치려면 _ 나 .. 를 사용해 일부 값을 무시하면 됩니다. 이것은 뒤의
[“패턴에서 값 무시하기”][ignoring-values-in-a-pattern] 절에서
보게 될 것입니다. 반대로 패턴에 변수가 너무 많아서 문제라면, 변수 개수를 줄여
양쪽 타입을 맞추면 됩니다.
조건부 if let 식
6장에서 if let 을 주로 “한 경우만 매칭하는 match 의 짧은 형태”로 설명했습니다.
선택적으로 if let 뒤에 else 를 붙여, 패턴이 맞지 않을 때 실행할 코드를 줄 수도
있습니다.
목록 19-3은 if let, else if, else if let, else 를 서로 섞어서 쓰는 것도
가능하다는 점을 보여 줍니다. 이렇게 하면 match 보다 더 유연하게 표현할 수 있습니다.
match 에서는 하나의 값만 여러 패턴과 비교할 수 있지만, if let 계열은 서로 관련
없는 조건들을 함께 표현할 수 있기 때문입니다.
이 목록은 여러 조건을 검사해 배경색을 어떤 색으로 정할지 결정하는 코드입니다. 예제에서는 실제 프로그램이라면 사용자 입력 등으로 받을 법한 값을 단순하게 하드코딩해 넣었습니다.
fn main() {
let favorite_color: Option<&str> = None;
let is_tuesday = false;
let age: Result<u8, _> = "34".parse();
if let Some(color) = favorite_color {
println!("Using your favorite color, {color}, as the background");
} else if is_tuesday {
println!("Tuesday is green day!");
} else if let Ok(age) = age {
if age > 30 {
println!("Using purple as the background color");
} else {
println!("Using orange as the background color");
}
} else {
println!("Using blue as the background color");
}
}
if let, else if, else if let, else 를 섞어 쓰기사용자가 선호 색상을 지정했다면 그 색을 배경색으로 사용합니다. 선호 색이 없고 오늘이 화요일이면 배경색은 초록색입니다. 그렇지 않고 사용자가 나이를 문자열로 주었으며 그 값을 숫자로 성공적으로 파싱할 수 있다면, 그 숫자에 따라 보라색이나 주황색을 사용합니다. 이 중 아무 조건도 맞지 않으면 배경색은 파란색입니다.
이런 조건 구조를 통해 꽤 복잡한 요구도 표현할 수 있습니다. 여기 넣어 둔 하드코딩된
값으로 이 예제를 실행하면 Using purple as the background color 를 출력합니다.
if let 역시 match arm과 마찬가지로 기존 변수를 가리는 새 변수를 도입할 수 있다는
점도 볼 수 있습니다. if let Ok(age) = age 라는 줄은, Ok 안 값으로 새 age
변수를 만들며 바깥의 age 변수를 가립니다. 따라서 if age > 30 조건은 반드시
그 블록 안에 있어야 합니다. if let Ok(age) = age && age > 30 처럼 하나로
합칠 수는 없습니다. 비교하고 싶은 새 age 는 중괄호 블록이 시작되어야 비로소
유효해지기 때문입니다.
if let 식의 단점은, match 식과 달리 컴파일러가 완전성을 검사해 주지 않는다는
점입니다. 만약 마지막 else 블록을 생략해 어떤 경우를 빼먹더라도, 컴파일러는 그
가능한 논리 버그를 알려 주지 않습니다.
while let 조건 루프
구조가 if let 과 비슷한 while let 조건 루프는, 어떤 패턴이 계속 매칭되는 동안
반복 실행되는 while 루프입니다. 목록 19-4는 스레드 사이로 보내진 메시지를 기다리는
while let 루프 예제인데, 이번에는 Option 이 아니라 Result 를 검사합니다.
fn main() {
let (tx, rx) = std::sync::mpsc::channel();
std::thread::spawn(move || {
for val in [1, 2, 3] {
tx.send(val).unwrap();
}
});
while let Ok(value) = rx.recv() {
println!("{value}");
}
}
rx.recv() 가 계속 Ok 를 반환하는 동안 값을 출력하는 while let 루프 사용하기이 예제는 1, 2, 3 을 출력합니다. recv 메서드는 채널 수신 쪽에서 첫 번째
메시지를 꺼내고 Ok(value) 를 반환합니다. 16장에서 recv 를 처음 다룰 때는
오류를 그냥 unwrap 하거나, 혹은 for 루프로 반복자처럼 다뤘습니다. 하지만
목록 19-4처럼 while let 로 처리할 수도 있습니다. 송신자가 살아 있는 동안은
recv 가 메시지가 도착할 때마다 Ok 를 반환하고, 송신 쪽이 끊어지면 Err 를
반환하기 때문입니다.
for 루프
for 루프에서는 for 키워드 바로 뒤에 오는 값이 패턴입니다. 예를 들어
for x in y 에서 x 가 패턴입니다. 목록 19-5는 for 루프 안의 패턴이 튜플을
구조분해하는 데도 사용될 수 있다는 점을 보여 줍니다.
fn main() {
let v = vec!['a', 'b', 'c'];
for (index, value) in v.iter().enumerate() {
println!("{value} is at index {index}");
}
}
for 루프 안 패턴으로 튜플 구조분해하기이 코드는 다음을 출력합니다.
$ cargo run
Compiling patterns v0.1.0 (file:///projects/patterns)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.52s
Running `target/debug/patterns`
a is at index 0
b is at index 1
c is at index 2
여기서 우리는 enumerate 메서드로 반복자를 감싸, 각 값과 그 인덱스를 튜플 형태로
만들었습니다. 첫 번째 값은 (0, 'a') 튜플입니다. 이 값이 (index, value) 패턴에
매칭될 때, index 는 0, value 는 'a' 가 되어 출력의 첫 줄이 만들어집니다.
함수 매개변수
함수 매개변수도 패턴이 될 수 있습니다. 목록 19-6의 foo 함수는 i32 타입
매개변수 하나를 받는데, 이제는 이 코드가 꽤 익숙하게 보여야 합니다.
fn foo(x: i32) {
// code goes here
}
fn main() {}
여기서 x 부분이 바로 패턴입니다! let 때와 마찬가지로, 함수 인수 위치에서도 튜플을
패턴으로 매칭할 수 있습니다. 목록 19-7은 함수에 튜플을 넘기면서 동시에 그 값을
분해하는 예입니다.
fn print_coordinates(&(x, y): &(i32, i32)) {
println!("Current location: ({x}, {y})");
}
fn main() {
let point = (3, 5);
print_coordinates(&point);
}
이 코드는 Current location: (3, 5) 를 출력합니다. 값 &(3, 5) 는 패턴
&(x, y) 와 매칭되므로, x 는 3, y 는 5 가 됩니다.
클로저도 함수와 비슷하기 때문에, 함수 매개변수 목록에서 하듯 클로저 매개변수 목록에도 같은 방식으로 패턴을 사용할 수 있습니다.