패턴이 사용될 수 있는 모든 곳
패턴은 러스트 코드 곳곳에 튀어나오며, 여러분도 모르는 사이에 이미 많이 사용하고 있었을 겁니다! 이번 절에서는 패턴을 사용할 수 있는 모든 코드상의 위치에 대해 설명합니다.
match
갈래
6장에서 설명한 것처럼 패턴은 match
표현식의 갈래에서 사용됩니다.
공식적으로 match
표현식은 다음과 같이 match
키워드, 매칭시킬 값,
그리고 패턴 및 그 패턴과 값이 매칭될 경우 실행될 표현식으로 구성된
하나 이상의 갈래로 정의됩니다:
match VALUE {
PATTERN => EXPRESSION,
PATTERN => EXPRESSION,
PATTERN => EXPRESSION,
}
예를 들면 아래는 예제 6-5에서 변수 x
에 있는 Option<i32>
값을
매칭시키는 match
표현식입니다:
match x {
None => None,
Some(i) => Some(i + 1),
}
이 match
표현식에 있는 패턴은 각 화살표 왼쪽에 위치한 None
과
Some(i)
입니다.
match
표현식에 대한 한 가지 요건은 match
표현식의 값에 대한 모든 경우의
수를 고려해야 한다는 의미에서 철저해야 (exhaustive) 한다는 것입니다.
모든 가능성을 포괄하는 것을 보장하는 방법 한 가지는 마지막 갈래에 캐치올
(catchall) 패턴을 사용하는 것입니다: 예를 들면 어떤 값과도 매칭될 수 있는 변수명은
절대 실패할 수 없게 되어 나머지 모든 경우를 포괄합니다.
_
라는 특정 패턴은 어떤 값과도 매칭되지만 값을 변수에 할당하지 않기 때문에 마지막 match
갈래에 자주 사용됩니다.
_
패턴은 예를 들면 지정되지 않은 값들을 무시하고
싶을 때 유용할 수 있습니다. 이 장의
‘패턴에서 값 무시하기’절에서
_
패턴에 대해 더 자세히 다루겠습니다.
if let
조건 표현식
6장에서 주로 하나의 경우에만 매칭하는 match
를 더 짧게 작성하는
방법으로 if let
표현식을 사용하는 법을 다루었습니다. 추가로
if let
은 if let
의 패턴에 값이 매칭되지 않을 때 실행되는
코드가 들어있는 else
를 가질 수 있습니다.
예제 18-1은 if let
, else if
, else if let
표현식을 섞어서
매칭할 수 있음을 알려주는 코드입니다. 그렇게 하면 패턴과 비교할
값을 하나만 표현할 수 있는 match
표현식보다 더 유연하게 사용할
수 있습니다. 또한 러스트에서는 일련의 if let
, else if
,
else if let
갈래들의 조건식이 서로 관련될 필요도 없습니다.
예제 18-1의 코드는 여러 개의 조건을 연속적으로 검사하여 배경의 색상을 결정합니다. 이 예제에서는 실제 프로그램에서 사용자 입력으로 받을 수 있는 하드코딩된 값이 들어있는 변수를 만들었습니다.
파일명: src/main.rs
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"); } }
사용자가 즐겨 찾는 색을 지정한 경우 그 색상이 배경으로 사용됩니다. 즐겨 찾는 색상이 지정되지 않았고 오늘이 화요일이면, 배경색은 녹색이 됩니다. 그렇지 않은 경우, 사용자가 자신의 나이를 문자열로 지정했고 이를 성공적으로 숫자로 파싱할 수 있다면, 색상은 숫자 값에 따라 보라 혹은 주황이 됩니다. 이 조건 중 어디에도 해당하지 않으면 배경색은 파란색이 됩니다.
이러한 조건부 구조는 복잡한 요구사항을 지원할 수 있게 해 줍니다. 위의
하드코딩된 값을 사용하면 이 예제는 Using purple as the background color
를
출력할 것입니다.
if let
또한 match
갈래와 같은 방식으로 섀도잉 변수를 도입할 수
있다는 것을 알 수 있습니다: 라인 if let Ok(age) = age
는 Ok
배리언트
내의 값을 추출한 새로운 섀도잉된 age
변수를 도입합니다. 이는
if age > 30
을 그 블록 안에 위치시켜야 함을 뜻합니다: 이 두 조건을
if let Ok(age) = age && age > 30
으로 조합할 수는 없습니다.
30과 비교하려는 섀도잉된 age
는 새로운 스코프가 중괄호로 시작되기
전에는 유효하지 않습니다.
if let
표현식의 단점은 match
표현식과는 다르게 컴파일러가 해당 구문이
모든 경우를 빠짐없이 포괄하는지 검사하지 않는다는 점입니다. 예제의 마지막
else
절을 생략하여 처리되지 않는 경우가 생기더라도 컴파일러는 이에 따라 발생할
수 있는 논리적 버그를 경고해 주지 않습니다.
while let
조건 루프
if let
과 구조가 비슷한 while let
조건 루프는 패턴이 계속
매칭되는 동안 while
루프를 실행할 수 있게 해 줍니다. 예제 18-2에서는
벡터를 스택처럼 사용하여 벡터의 값을 푸시된 역순으로
출력하는 while let
루프를 코딩했습니다.
fn main() { let mut stack = Vec::new(); stack.push(1); stack.push(2); stack.push(3); while let Some(top) = stack.pop() { println!("{}", top); } }
이 예제는 3, 2, 1을 출력합니다. pop
메서드는 벡터에서 마지막 요소를
가져와서 Some(value)
를 반환합니다. 벡터가 비어있다면 pop
은 None
을
반환합니다. while
루프는 pop
이 Some
을 반환하는 한 블록의 코드를
계속 실행합니다. pop
이 None
을 반환하면 루프는 멈춥니다. while let
을
사용하여 스택의 모든 요소를 팝할 수 있습니다.
for
루프
for
루프에서 키워드 for
바로 뒤에 오는 값은 패턴입니다.
예를 들어 for x in y
에서는 x
가 패턴입니다. 예제 18-3은
for
루프에서 패턴을 사용하여 for
루프의 일부로서 튜플을
해체 혹은 분해하는 방법을 보여줍니다.
fn main() { let v = vec!['a', 'b', 'c']; for (index, value) in v.iter().enumerate() { println!("{} is at index {}", value, index); } }
예제 18-3의 코드는 다음을 출력할 것입니다:
$ cargo run
Compiling patterns v0.1.0 (file:///projects/patterns)
Finished dev [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'
가 되어 출력 결과의 첫 번째
줄을 출력합니다.
let
구문
이번 장의 전까지는 match
나 if let
과 함께 패턴을 사용하는 것에 대해서만
명시적으로 설명했지만, 실은 다른 곳에서도 패턴을 사용해 왔는데 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
에 바인딩해라’라는 뜻이 됩니다.
패턴 매칭의 관점에서 let
좀 더 명확하게 보기 위해서, let
으로
튜플을 분해하는 패턴을 사용하는 예제 18-4를 살펴봅시다.
fn main() { let (x, y, z) = (1, 2, 3); }
여기서는 튜플을 패턴에 매칭합니다. 러스트는 값 (1, 2, 3)
을 패턴
(x, y, z)
와 비교하고 이 값이 패턴과 매칭되는지 확인하고, 1
을
x
에, 2
를 y
에, 그리고 3
을 z
에 바인딩합니다. 이 튜플 패턴을
이 안에 있는 세 개의 개별적인 변수 패턴이 중첩된 것으로 생각할 수 있습니다.
패턴의 요소 개수가 주어진 튜플의 요소 개수와 다르면, 전체 타입이 일치하지 않아서 컴파일러 에러가 발생합니다. 예를 들어 예제 18-5는 세 개의 요소가 있는 튜플을 두 개의 변수로 해체하는 시도를 보여주는데, 이는 작동하지 않을 것입니다.
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` due to previous error
이 에러를 고치기 위해서는 _
나 ..
를 사용하여 튜플의 값을
하나 혹은 그 이상 무시할 수 있는데, 이에 대해서는
‘패턴에서 값 무시하기’절에서
살펴볼 것입니다. 패턴에 너무 많은 변수가 있는 것이 문제라면, 해결책은
변수를 제거하여 변수 수가 튜플의 요소 개수와 같도록 타입을 일치시키는
것입니다.
함수 매개변수
함수 매개변수도 패턴이 될 수 있습니다. 예제 18-6의 코드는
foo
라는 이름의 함수를 선언하고 타입 i32
인 x
라는 매개변수 하나를
받는데, 이제는 친숙하게 보일 것입니다.
fn foo(x: i32) { // 여기에 코드를 작성합니다 } fn main() {}
x
부분이 패턴입니다! let
에서 했던 것처럼 함수 인수의 튜플을 패턴과
매치시킬 수 있습니다. 예제 18-7은 함수에 값을 넘길 때 튜플의 값을
분할합니다.
파일명: src/main.rs
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
가 됩니다.
13장에서 설명한 것처럼 클로저는 함수와 유사하기 때문에, 클로저 매개변수 목록에서도 함수 매개변수 목록과 동일한 방식으로 패턴을 사용할 수 있습니다.
지금까지 패턴을 사용하는 여러 가지 방법을 살펴보았지만, 패턴을 사용할 수 있는 모든 곳에서 패턴이 동일하게 작동하는 것은 아닙니다. 어떤 곳에서는 패턴이 반박이 불가능 (irrefutable) 해야 합니다; 다른 곳에서는 반박이 가능 (refutable) 할 수 있습니다. 다음에는 이 두 가지 개념에 대해 설명하겠습니다.