match 표현식

match 표현식 (match expressions)

값에 따라 분기를 나눠야 할 때, 러스트에서는 match 표현식을 써요. 이 페이지에서는 패턴에 따라 분기하는 match가 어떤 식으로 동작하는지 정확히 살펴볼게요.

출처: Rust Reference

본문

match 표현식은 패턴에 따라 분기해요. 발생하는 매칭의 정확한 형태는 패턴에 따라 달라져요.

문법은 다음과 같아요.

MatchExpression → match Scrutinee { InnerAttribute* MatchArms? }
Scrutinee → Expression except StructExpression
MatchArms → ( MatchArm => ( ExpressionWithoutBlock , | ExpressionWithBlock ,? ) )*
            MatchArm => Expression ,?
MatchArm → OuterAttribute* Pattern MatchArmGuard?
MatchArmGuard → if MatchConditions
MatchConditions → MatchGuardChain | Expression
MatchGuardChain → MatchGuardCondition ( && MatchGuardCondition )*
MatchGuardCondition → Expression except ExcludedMatchConditions
                   | OuterAttribute* let Pattern = MatchGuardScrutinee
MatchGuardScrutinee → Expression except ExcludedMatchConditions
ExcludedMatchConditions → LazyBooleanExpression | RangeExpr | RangeFromExpr
                        | RangeInclusiveExpr | AssignmentExpression
                        | CompoundAssignmentExpression

match 표현식에는 **검사 대상 표현식(scrutinee expression)**이 있는데, 이는 패턴들과 비교할 값이에요. 검사 대상 표현식과 패턴들은 같은 타입이어야 해요.

match는 검사 대상 표현식이 위치 표현식(place expression)인지 값 표현식(value expression)인지에 따라 다르게 동작해요.

검사 대상 표현식이 값 표현식이라면, 먼저 임시 위치에 평가된 다음, 결과 값이 arm의 패턴들과 순서대로 비교돼요. 첫 번째로 일치하는 패턴을 가진 arm이 분기 대상으로 선택되고, 패턴이 바인딩한 변수들은 arm 블록의 지역 변수에 할당되며, 제어 흐름이 그 블록으로 들어가요.

검사 대상 표현식이 위치 표현식일 때는 match가 임시 위치를 할당하지 않아요. 다만 값에 의한 바인딩은 그 메모리 위치에서 복사하거나 이동할 수 있어요. 가능하다면 위치 표현식에 대해 매칭하는 것이 좋은데, 이 경우 매치의 수명(lifetime)이 위치 표현식의 수명을 상속받아서 match 내부로 제한되지 않기 때문이에요.

간단한 예시를 볼게요.

let x = 1;

match x {
    1 => println!("one"),
    2 => println!("two"),
    3 => println!("three"),
    4 => println!("four"),
    5 => println!("five"),
    _ => println!("something else"),
}

패턴 안에서 바인딩된 변수들은 매치 가드(match guard)와 arm의 표현식으로 스코프가 제한돼요. 바인딩 모드(이동, 복사, 또는 참조)는 패턴에 따라 달라져요.

여러 매치 패턴은 | 연산자로 연결할 수 있어요. 각 패턴은 성공적인 매치를 찾을 때까지 왼쪽에서 오른쪽 순서로 검사돼요.

let x = 9;
let message = match x {
    0 | 1  => "not many",
    2 ..= 9 => "a few",
    _      => "lots"
};

assert_eq!(message, "a few");

// 패턴 매치 순서의 데모.
struct S(i32, i32);

match S(1, 2) {
    S(z @ 1, _) | S(_, z @ 2) => assert_eq!(z, 1),
    _ => panic!(),
}

참고: 2..=9는 범위 패턴(Range Pattern)이지 범위 표현식이 아니에요. 따라서 매치 arm에서는 범위 패턴이 지원하는 종류의 범위만 사용할 수 있어요.

|로 구분된 각 패턴의 모든 바인딩은 arm의 모든 패턴에 나타나야 해요. 그리고 같은 이름의 바인딩은 모두 같은 타입이고 같은 바인딩 모드를 가져야 해요.

전체 match 표현식의 타입은 개별 match arm들의 최소 상한(lub)이에요. 만약 match arm이 하나도 없다면 match 표현식은 발산(diverging)하며 타입은 !가 돼요.

fn make<T>() -> T { loop {} }
enum Empty {}

fn diverging_match_no_arms() -> ! {
    let e: Empty = make();
    match e {}
}

검사 대상 표현식이나 모든 match arm이 발산한다면, 전체 match 표현식도 발산해요.

매치 가드 (Match guards)

Match arm은 매치 조건을 더 세밀하게 다듬기 위해 매치 가드를 받을 수 있어요. 패턴 가드는 if 키워드 뒤의 패턴 다음에 나타나며, 부울 타입의 표현식 또는 조건부 let 매치로 구성돼요.

패턴이 성공적으로 매치되면 패턴 가드가 실행돼요. 모든 가드 조건 피연산자가 true로 평가되고 모든 let 패턴이 자신의 검사 대상에 성공적으로 매치되면, 해당 match arm이 성공적으로 매치된 것으로 보고 arm 본문이 실행돼요. 그렇지 않으면 같은 arm 안의 | 연산자를 이용한 다른 매치를 포함해 다음 패턴이 검사돼요.

let maybe_digit = Some(0);
fn process_digit(i: i32) { }
fn process_other(i: i32) { }
let message = match maybe_digit {
    Some(x) if x < 10 => process_digit(x),
    Some(x) => process_other(x),
    None => panic!(),
};

참고: | 연산자를 이용한 여러 매치는 패턴 가드와 그 부수 효과가 여러 번 실행되게 할 수 있어요.

use std::cell::Cell;
let i : Cell<i32> = Cell::new(0);
match 1 {
    1 | _ if { i.set(i.get() + 1); false } => {}
    _ => {}
}
assert_eq!(i.get(), 2);

패턴 가드는 그 뒤를 따르는 패턴 안에서 바인딩된 변수들을 참조할 수 있어요. 가드를 평가하기 전에, 변수가 매치되는 검사 대상의 부분에 대한 공유 참조가 취해져요. 가드를 평가하는 동안에는 이 공유 참조가 변수에 접근할 때 사용돼요. 가드가 성공적으로 평가되어야만 값이 검사 대상에서 변수로 이동(또는 복사)돼요. 이 덕분에 가드가 매치에 실패할 경우 검사 대상에서 값을 이동시키지 않으면서 가드 안에서 공유 빌림을 사용할 수 있죠. 게다가 가드 평가 중에는 공유 참조를 유지하기 때문에 가드 안에서의 변경도 막을 수 있어요.

가드는 let 패턴을 사용해서 검사 대상을 조건부로 매치하고, 패턴이 성공적으로 매치될 때 새 변수를 스코프에 바인딩할 수도 있어요.

enum Command {
    Run(String),
    Stop,
}
let cmd = Command::Run("example".to_string());

match cmd {
    Command::Run(name) if let Some(first_char) = name.chars().next() => {
        // 여기서 `name`과 `first_char` 둘 다 사용 가능
        println!("Running: {name} (starts with '{first_char}')");
    }
    Command::Run(name) => {
        println!("{name} is empty");
    }
    _ => {}
}

매치 가드 체인 (Match guard chains)

여러 가드 조건 피연산자는 &&로 구분할 수 있어요.

let foo = Some([123]);
let already_checked = false;
match foo {
    Some(xs) if let [single] = xs && !already_checked => { dbg!(single); }
    _ => {}
}

&& 지연 부울 표현식과 비슷하게, 각 피연산자는 피연산자가 false로 평가되거나 let 매치가 실패할 때까지 왼쪽에서 오른쪽으로 평가돼요. 그 경우 그 뒤의 피연산자들은 평가되지 않아요. 각 let 패턴의 바인딩은 다음 조건 피연산자와 match arm 본문에서 사용할 수 있도록 스코프에 들어가요.

어떤 가드 조건 피연산자가 let 패턴이라면, let 검사 대상과의 모호성 및 우선순위 때문에 어떤 조건 피연산자도 || 지연 부울 연산자 표현식일 수 없어요. 만약 || 표현식이 필요하면 괄호를 사용할 수 있어요.

let foo = Some([123]);
match foo {
    Some(xs) if let [x] = xs
        // 여기서 괄호가 필요해요.
        && (x < -100 || x > 20) => {}
    _ => {}
}

match arm의 속성 (Attributes on match arms)

외부 속성(outer attribute)은 match arm에 허용돼요. match arm에서 의미를 갖는 속성은 cfg와 린트 검사 속성뿐이에요. 내부 속성(inner attribute)은 블록 표현식의 속성과 같은 표현식 문맥에서 match 표현식의 여는 중괄호 바로 뒤에 허용돼요.

더 알아보기 (Learn more)

  • Rust Reference의 매치 표현식 문서에서 전체 문법과 세부 규칙을 확인할 수 있어요.
  • Rust Reference의 패턴 문서에서 match에서 사용하는 각종 패턴을 다룬답니다.
  • Rust Reference의 표현식 문서에서 다른 표현식들과 match의 관계를 살펴볼 수 있어요.