이 문서는 2판 번역본입니다.
최신 2021 에디션 문서는 https://doc.rust-kr.org 에서 확인하실 수 있습니다.
패턴이 사용될 수 있는 모든 곳
패턴은 러스트 코드 곳곳에 튀어나옵니다. 아마 모르는 사이 이미 아주 많이 쓰고있었을 겁니다! 이번 절에선 패턴을 사용할 수 있는 모든 코드상의 장소에 대한 레퍼런스를 제공합니다.
match
갈래
6장에서도 다루었듯, 패턴은 match
표현의 각 갈래에 사용됩니다. match
표현은
match
키워드, 대응 시킬 값, 갈래들로 이루어지고, 각 갈래는 갈래를 나타내는 패턴,
해당 패턴에 값이 대응 될때 실행할 표현으로 구성됩니다.
결국 다음과 같은 구조를 가집니다:
match 값 {
패턴 => 표현,
패턴 => 표현,
패턴 => 표현,
}
match
표현을 사용하기 위해 지켜야할 사항이 있습니다. match
표현에 대응 시킬 값이 가질
수 있는 모든 경우의 수를 빠짐 없이 표현해야 합니다. 이 조건을 보장하는 하나의 방법은
match
의 마지막 갈래에 모든 경우에 대응되는 패턴을 이용하는 것입니다. 예를 들자면
아무 값에나 대응될 변수명 하나를 놓는 패턴이죠. 이 경우 주어진 그 어떠한 값도 해당 변수에
묶이게 되어 실패할 수 없게 되고, 즉 가능한 모든 경우를 표현한게 됩니다.
_
라는 특별한 패턴은 아무 값에나 대응되지만 그 어떠한 변수에도 묶이지 않습니다.
그래서 match
의 마지막 갈래에 종종 쓰입니다.
_
패턴은 명시 하지 않은 값들을 무시할 때 유용합니다.
이 특별한 패턴은 뒤의 "패턴에서 값 무시하기" 절에서 더 자세하게
다룰 것입니다.
if let
조건 표현
6장에서 if let
표현을 사용하는 법을 다뤘습니다.
주로 갈래가 하나 밖에 없는 match
표현을 더 짧게 표현하는 방법으로 사용됐습니다.
추가적으로 if let
은 else
절을 가질 수 있습니다.
예상 하셨듯 if let
의 패턴에 값이 대응되지 않을 때 실행됩니다.
예제 18-1은 if let
, else if
, else if let
표현을 섞어서 사용할 수 있음을 알려주는
코드입니다.
이들을 이용 하면 여러 패턴에 하나의 값밖에 대응시키지 못 하는 match
표현보다
더 유연하게 표현 가능합니다.
또 연속된 if let
, else if
, else if let
의 각 조건이 꼭 연관될 필요도 없습니다.
예제 18-1의 코드는 배경 색이 어떤 색이 될지 정하기 위해 여러개의 조건을 연이어 체크하는 것을 보여줍니다. 이번 예제에서는 실제 프로그램이 받을 지도 모르는 유저의 입력 값들이 하드코딩된 변수들을 이용합니다.
유저가 좋아하는 색을 지정할 경우 그 색이 배경 색이 됩니다. 만일 오늘이 화요일이라면 배경은 녹색이 됩니다. 만일 유저가 나이를 스트링으로 입력했고, 이를 성공적으로 숫자로 파싱해낼 수 있다면 숫자 값에 따라 보라나 주황으로 배경 색이 정해집니다. 위의 그 어떤 조건에도 맞지 않을 경우 배경은 파란색이 됩니다:
Filename: 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, {}, as the background", color); } 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
변수가
전의 age
를 가리게 됩니다. 이는 if age > 30
을 조건절 밑의 새로운 스코프에서 사용
해야 함을 의미합니다. 우리의 새로운 age
는 새로운 중괄호가 나타나 새로운 스코프가
시작하기 전까지 유효하지 않으므로 위의 조건절 밑의 스코프 안에서 사용해야 합니다.
if let Ok(age) = age && age > 30
처럼 두 조건을 합쳐서 하나의 절에서 사용 할 수
없습니다.
if let
표현의 단점은 match
표현과 다르게 컴파일러가 해당 구문이 모든 경우를 다뤘는지
판단하지 않는다는 점입니다. 예제의 마지막 else
절을 빼먹어서 처리 되지 않는 경우가
생기더라도 컴파일러가 이를 찾아내지 않고 이에 따라 발생 할 수 있는 논리 버그를 사전에
경고해주지 않습니다.
while let
조건 루프
if let
과 구조적으로 비슷하게 생긴 while let
조건 루프는 주어진 값이 패턴에
계속 대응 되는한 실행 되는 while
루프입니다.
예제 18-2는 vector를 스택처럼 이용해 push된 역순으로 값을 출력하는
while let
조건 루프입니다.
#![allow(unused)] 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
메소드는 vector의 마지막 값을 꺼내서
Some(value)
를 반환합니다. 만일 vector가 비었을 경우 None
을 반환합니다.
코드의 while
루프는 pop
이 Some
을 반환 할 동안 실행됩니다. None
이 반환되는
경우에 더 이상 패턴에 맞지 않으므로 멈춥니다. while let
표현으로 스택의 모든
값들을 간편하게 꺼낼 수 있습니다.
for
루프
3장에서 for
루프가 러스트에서 가장 흔하게 쓰이는 반복문 구문인 것을 언급 했습니다.
다만 그 for
루프들에서 사용하는 패턴에 대해선 아직 다루지 않았습니다.
for
루프의 패턴은 for
키워드 바로 다음에 오는 값입니다.
즉 for x in y
에서 x
가 패턴입니다.
예제 18-3은 for
에서 패턴을 이용해 튜플을 분해하여 튜플을 for
루프의 일부로써
사용하는 것을 보여줍니다.
#![allow(unused)] fn main() { let v = vec!['a', 'b', 'c']; for (index, value) in v.iter().enumerate() { println!("{} is at index {}", value, index); } }
예제의 출력결과는 다음과 같습니다.
a is at index 0
b is at index 1
c is at index 2
enumerate
메소드는 해당 반복자를 자신의 값과 값의 index를 포함한 튜플들을 순회하도록
바꿔 줍니다. 그래서 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
자리에 변수명이 들어가는 경우, 그 변수명 자체가 하나의
무지하게 단순한 패턴의 한 형태입니다. 러스트는 EXPRESSION
을 PATTERN
에 비교하고
거기서 발견한 이름들에 그 EXPRESSION
을 대입합니다.
let x = 5;
예제에서 x
라는 패턴은 "이 패턴에 대응 되는 값을 변수 x
에 대입하는"
패턴입니다. 이 경우 x
가 패턴의 전부이므로 "아무 값을 통으로 x
에 대입한다"는
의미를 갖게 됩니다.
let
의 좀 더 "패턴 매칭"스러운 면모를 보기 위해 예제 18-4를 봅시다. 이 예제는
튜플을 분해하는 패턴을 사용하고 있습니다.
#![allow(unused)] fn main() { let (x, y, z) = (1, 2, 3); }
이 코드는 튜플을 하나의 패턴에 대응시키고 있습니다. 러스트는 튜플 (1, 2, 3)
을
(x, y, z)
에 비교하고 튜플이 해당 패턴에 대응된다는 것을 알아 냅니다. 그래서 러스트는
1
을 x
에, 2
를 y
에, 3
을 z
에 각각 묶습니다. 이 튜플예제를 3개의 변수 대입
패턴을 하나의 패턴으로 묶은 것으로 생각하셔도 좋습니다.
만약 패턴의 원소의 수가 주어진 튜플의 원소의 수와 다를 경우, 둘의 타입이 서로 달라 대응에 실패하고 컴파일 에러가 발생하게 됩니다. 예제18-5는 원소가 3개인 튜플을 원소가 2개인 패턴에 대응시키려고 하고 있습니다. 물론 실패합니다:
let (x, y) = (1, 2, 3);
컴파일을 시도할 경우 다음과 같은 타입에러가 발생합니다:
error[E0308]: mismatched types
--> src/main.rs:2:9
|
2 | let (x, y) = (1, 2, 3);
| ^^^^^^ expected a tuple with 3 elements, found one with 2 elements
|
= note: expected type `({integer}, {integer}, {integer})`
found type `(_, _)`
튜플의 원소중 하나, 혹은 여럿을 무시하고 싶을 경우 "패턴에서 값 무시하기"에서
자세하게 다룰 _
나 ..
를 사용할 수 있습니다.
반대로 문제가 패턴 쪽의 변수가 너무 많아서 생겼다면 패턴의 변수 일부를 없에는
것으로 튜플의 원소의 수와 패턴의 변수 원소의 수를 맞추어 둘의 타입을 맞춰주면
됩니다.
함수의 매개변수
함수의 매개변수들도 물론 패턴입니다.
예제 18-6은 i32
타입의 매개변수 x
를 가지는 함수 foo
를 정의 하는 코드입니다.
이제는 익숙한 코드라 생각합니다.
#![allow(unused)] fn main() { fn foo(x: i32) { // code goes here } }
예상대로 x
부분은 패턴입니다! let
에서도 그랬듯, 함수에 인자를 넘기는 과정에서
튜플을 패턴에 대응시킬 수 있습니다.
예제 18-7은 함수에 튜플을 넘기면서 그 값들을 분해해서 사용하는 코드입니다.
Filename: 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) 해도 됩니다. 이 둘의 차이를 다음 절에서 자세히 다루도록 합시다.