패턴 매칭의 기초

패턴 매칭은 Rust에서 다양한 데이터 구조나 값의 상태를 간편하고 안전하게 분기 처리하기 위한 핵심 기능이다. Rust는 값이 가진 구조를 빠짐없이 분석하도록 유도함으로써 여러 오류 상황을 사전에 방지해 준다. 가령 열거형이나 Option 타입 같은 조건 분기를 자주 요구하는 구조를 다룰 때, 러스트의 패턴 매칭 문법은 코드 가독성을 높이고 컴파일 타임 체크를 강화하는 역할을 한다.

패턴 매칭은 크게 match 표현식과 if let, while let과 같은 간단한 형태로 구분할 수 있다. 여기서는 match 표현식에 초점을 맞춰 기초를 살펴본다. 또한 패턴 매칭에서 자주 쓰이는 다양한 문법 요소들을 자세히 짚어본다.

Rust의 match 표현식은 크게 ‘패턴’과 ‘결과 표현식(또는 블록)’이라는 두 가지 요소로 구성된다. match 뒤에 오는 값(혹은 식)이 어떤 형태, 어떤 변형 유형을 가지는지를 패턴으로 명시하고, 각 패턴에 부합하는 경우에 실행할 동작을 결과 표현식으로 정의한다.

다음은 기본적인 match 표현식의 간단한 예시이다.

fn main() {
    let value = 3;

    match value {
        1 => println!("값이 1이다."),
        2 => println!("값이 2이다."),
        _ => println!("값이 1도 2도 아니다."),
    }
}

이 코드에서 value가 1이면 첫 번째 분기로, 2이면 두 번째 분기로 처리되며, 그 외 값이면 _ 패턴 분기로 처리된다. Rust의 match는 가능한 모든 값의 경우를 빠짐없이 처리를 요구한다. 여기서 _는 “해당되지 않는 모든 패턴”을 의미하므로, 남은 모든 상황을 받아들이는 “디폴트 케이스” 역할을 한다.

match는 반드시 모든 경우를 처리해야 하므로, 열거형처럼 한정된 경우의 수를 갖는 타입을 다룰 때 특히 유용하다. 예를 들어 Option 타입은 Some(T) 혹은 None 두 가지 경우만 존재하므로, match를 이용하면 명시적으로 모든 경우를 처리했는지 컴파일러가 검사한다.

fn describe_option(value: Option<i32>) {
    match value {
        Some(n) => println!("값이 있다: {}", n),
        None => println!("값이 없다"),
    }
}

위의 예시에서 Option가 Some(n)이면 내부 정수 n에 접근해서 출력을 수행하고, None이면 값이 없음을 알리는 메시지를 출력한다. Some(n)과 None 이외에 다른 경우의 수는 존재하지 않으므로, match의 각 패턴이 해당 열거형의 모든 경우를 포괄한다.

패턴 매칭은 단순히 값을 비교하는 수준을 넘어 다양한 구조를 “해체”하고 내부 데이터를 추출할 수 있다. 튜플, 구조체, 열거형도 match로 분해해서 필요한 정보를 가져올 수 있다. 예를 들어 튜플을 다룰 때는 다음과 같은 형태로 사용이 가능하다.

이 코드에서 (0, y)는 튜플의 첫 번째 원소가 0이며, 두 번째 원소를 y라는 지역 변수로 바인딩한다는 의미다. 두 번째 분기 (x, 0)는 반대로 첫 번째 원소를 x라는 이름으로 받고, 두 번째 원소가 0이라는 상황을 의미한다. 마지막으로 (x, y)는 앞선 패턴들을 제외한 모든 경우(두 원소가 모두 0이 아니거나, 하나만 0이 아닌 경우 포함)에 대응한다.

구조체도 필드 명을 활용해 내부 데이터를 분해할 수 있다.

구조체 패턴 매칭에서는 필드 이름을 정확히 명시해야 한다. 이때 특정 필드만 분해하고, 나머지 필드는 무시하려면 .. 구문을 사용할 수도 있다.

열거형은 특히 match 표현식에서 큰 힘을 발휘하는 예시로 자주 거론된다. Rust에서는 열거형(Enums) 값이 어떤 variant인지 명시적으로 판별해 모든 경우를 처리해야 하기 때문이다. 다음은 열거형으로 정의된 Message 타입을 예로 들어 각 variant를 분기 처리하는 match 문을 살펴보자.

위 예시처럼 열거형 각각의 variant를 패턴으로 명시해, 해당 variant일 경우의 동작만을 구현하면 된다. Message::Move { x, y }와 같이 필드명을 포함하는 variant일 경우에는 구조체 패턴과 유사하게 중괄호 안에서 필드를 해체하면 된다. 반면 튜플 형태인 Message::ChangeColor(r, g, b)처럼 인자를 나열하는 방식으로 분해할 수도 있다.

패턴 매칭에서 자주 활용되는 요소 중 하나는 _(언더스코어) 패턴이다. 이는 “아무 값이든지 매칭”함을 의미한다. 언더스코어 하나만 쓰거나, 구조 분해 과정에서 특정 필드를 무시할 때 _를 사용할 수 있다. _ 패턴을 남발하면 패턴 매칭이 지닌 컴파일 타임 안전성 검사를 약화시킬 수 있으므로, 꼭 필요한 경우에만 쓰는 것이 좋다.

패턴 매칭에는 조건부로 패턴을 한정하는 “가드”도 존재한다. match 분기 옆에 if 절을 덧붙여 특정 조건을 만족해야만 해당 분기로 매칭이 성사되도록 할 수 있다. 예를 들어 다음과 같은 구조를 살펴보자.

이 예시에서는 패턴 자체가 x라는 값을 그대로 바인딩한 뒤, 그 값이 음수나 양수인지 각각 분기하는 가드를 붙였다. 가드에서는 match로 선언된 변수뿐만 아니라 바깥 스코프에 있는 데이터도 자유롭게 접근이 가능하다.

패턴 매칭은 Rust가 지닌 안전성과 표현력을 잘 보여주는 문법 요소다. match를 통해 확실한 분기 처리를 구현하면, 잠재적 버그나 런타임 예외 상황을 줄이면서 코드 가독성을 높일 수 있다. Option, Result, 사용자 정의 열거형을 비롯해 다양한 상황에서 match 패턴을 활용해 보길 권장한다. Rust 컴파일러는 match를 통해 “모든 경우를 처리했는지” 여부를 철저히 검사하여, 누락된 분기가 있을 경우 컴파일 에러를 일으키기 때문에, 프로그램 동작 중에 발생할 수 있는 예외적인 상태를 사전에 예방할 수 있다.

이상으로 패턴 매칭의 기초 개념을 살펴봤다. match 표현식을 다양한 자료 구조에 적용하면서 내부 데이터를 쉽게 분해하고, 컴파일러로부터 안전성을 보장받을 수 있다는 점이 Rust를 배우는 데 중요한 지점이다. 패턴 매칭을 통달하면 더 복잡한 자료 구조나 상황 분기도 깔끔하고 안전하게 처리할 수 있을 것이다.

Last updated