Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

패턴이 사용될 수 있는 모든 위치

패턴은 러스트 곳곳에 등장하며, 여러분도 모르는 사이에 이미 많이 써 왔습니다! 이 절에서는 패턴을 사용할 수 있는 모든 위치를 살펴봅니다.

match arm

6장에서 설명했듯, 우리는 match 식의 arm 안에서 패턴을 사용합니다. 형식적으로 보면 match 식은 match 키워드와, 비교할 값 하나, 그리고 “패턴 + 값이 맞을 때 실행할 식”으로 이루어진 arm 하나 이상으로 구성됩니다. 형태는 다음과 같습니다.

match VALUE {
    PATTERN => EXPRESSION,
    PATTERN => EXPRESSION,
    PATTERN => EXPRESSION,
}

예를 들어 다음은 6장의 목록 6-5에서 변수 xOption<i32> 값에 대해 매칭하던 match 식입니다.

match x {
    None => None,
    Some(i) => Some(i + 1),
}

match 식에서 패턴은 각 화살표 왼쪽의 NoneSome(i) 입니다.

match 식에는 한 가지 요구사항이 있습니다. 반드시 완전해야 한다는 점입니다. 즉 match 안에서 다루는 값이 가질 수 있는 모든 가능성이 반드시 어떤 arm 에서든 처리되어야 합니다. 이를 만족하는 한 가지 방법은 마지막 arm에 “모든 것을 받는” catch-all 패턴을 두는 것입니다. 예를 들어 어떤 값과도 매칭될 수 있는 변수 이름은 실패할 수 없기 때문에 나머지 모든 경우를 덮을 수 있습니다.

특히 _ 패턴은 무엇이든 매칭되지만, 그 값을 변수에 바인딩하지는 않습니다. 따라서 어떤 값을 무시하고 싶을 때 마지막 match arm에서 자주 사용합니다. 이 _ 패턴은 뒤의 [“패턴에서 값 무시하기”][ignoring-values-in-a-pattern] 절에서 더 자세히 다룹니다.

let

이 장 전까지 우리는 패턴을 matchif 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);
}
Listing 19-1: 패턴으로 튜플을 구조분해하여 한 번에 세 변수를 만들기

여기서 러스트는 값 (1, 2, 3) 을 패턴 (x, y, z) 와 비교하고, 양쪽 요소 개수가 같다는 것을 확인한 뒤 1x, 2y, 3z 에 바인딩합니다. 이 튜플 패턴은, 그 안에 세 개의 개별 변수 패턴이 중첩되어 있는 것으로 생각해도 됩니다.

패턴 안 요소 수가 튜플 안 요소 수와 맞지 않으면, 전체 타입이 맞지 않으므로 컴파일 오류가 납니다. 예를 들어 목록 19-2는 요소가 세 개인 튜플을 두 변수에만 구조분해하려는 잘못된 시도입니다.

fn main() {
    let (x, y) = (1, 2, 3);
}
Listing 19-2: 튜플 요소 개수와 맞지 않는 변수 개수를 가진 패턴을 잘못 작성하기

이 코드를 컴파일하려 하면 다음과 같은 타입 오류가 납니다.

$ 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 계열은 서로 관련 없는 조건들을 함께 표현할 수 있기 때문입니다.

이 목록은 여러 조건을 검사해 배경색을 어떤 색으로 정할지 결정하는 코드입니다. 예제에서는 실제 프로그램이라면 사용자 입력 등으로 받을 법한 값을 단순하게 하드코딩해 넣었습니다.

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, {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");
    }
}
Listing 19-3: 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}");
    }
}
Listing 19-4: 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}");
    }
}
Listing 19-5: 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) 패턴에 매칭될 때, index0, value'a' 가 되어 출력의 첫 줄이 만들어집니다.

함수 매개변수

함수 매개변수도 패턴이 될 수 있습니다. 목록 19-6의 foo 함수는 i32 타입 매개변수 하나를 받는데, 이제는 이 코드가 꽤 익숙하게 보여야 합니다.

fn foo(x: i32) {
    // code goes here
}

fn main() {}
Listing 19-6: 매개변수 위치에 패턴을 사용하는 함수 시그니처

여기서 x 부분이 바로 패턴입니다! let 때와 마찬가지로, 함수 인수 위치에서도 튜플을 패턴으로 매칭할 수 있습니다. 목록 19-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);
}
Listing 19-7: 튜플을 구조분해하는 매개변수를 가진 함수

이 코드는 Current location: (3, 5) 를 출력합니다. 값 &(3, 5) 는 패턴 &(x, y) 와 매칭되므로, x3, y5 가 됩니다.

클로저도 함수와 비슷하기 때문에, 함수 매개변수 목록에서 하듯 클로저 매개변수 목록에도 같은 방식으로 패턴을 사용할 수 있습니다.