구조체와 열거형의 복잡한 패턴 매칭과 가변 바인딩

구조체(Struct)와 열거형(Enum)의 여러가지 패턴 매칭(Match) 방법과 가변 바인딩(mutable binding)에 대해 알아보겠습니다. 구조체 분해후 바인딩, if let, _, .., | 등의 사용법과 참조와 Shadowing에 대해서도 알아봅니다.

1. 구조체(Struct)에서 복잡한 패턴 매칭과 가변 바인딩

가. 구조체 필드 일부만 매칭하기:

구조체 패턴에서 모든 필드를 반드시 매칭하지 않아도 되며, ..을 써서 나머지 필드는 무시할 수 있습니다.

struct Point {
x: i32,
y: i32,
z: i32,
}

fn main() {
let p = Point { x: 1, y: 2, z: 3 };

match p {
Point { x, .. } => println!("x 값에만 관심 있음: {}", x),
}
}

나. 가변 바인딩 (mutable binding)

기본적으로 Rust의 match에서 바인딩된 변수는 불변입니다. 가변으로 만들려면 mut 키워드를 변수 바인딩 전에 붙입니다.

let mut p = Point { x: 1, y: 2, z: 3 };

match &mut p { // 구조체를 가변 참조로 매칭
Point { x, y, .. } => {
*x += 10; // 가변 참조로 접근 가능
*y += 20;
println!("변경된 x: {}, y: {}", x, y);
}
}
  • 위 예에서 &mut p로 가변 참조를 패턴 매칭했고, 패턴 안의 x와 y는 가변 참조(&mut i32)가 되어 값을 변경할 수 있습니다.

다. 구조체 분해 후 변수 재바인딩

let p = Point { x: 10, y: 20, z: 30 };
let Point { x: a, y: b, z: c } = p;
println!("a: {}, b: {}, c: {}", a, b, c);

a, b, c가 각각 p의필드 값에 바인딩됩니다.

2. 열거형(Enum)에서 복잡한 패턴 매칭과 가변 바인딩

가. 열거형 variant에 포함된 여러 필드 매칭

enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}

fn main() {
let mut msg = Message::Move { x: 10, y: 20 };

match &mut msg {
Message::Move { x, y } => {
*x += 5; // 가변 참조로 내부 값 변경 가능
*y += 5;
println!("이동 좌표 변경: x={}, y={}", x, y);
}
_ => {}
}
}
  • match &mut msg를 통해 열거형 값을 가변 참조로 매칭하면, 내부 필드도 가변 참조로 바인딩되어 값 변경이 가능합니다.

나. 특정 variant만 언패킹하기

if let Message::Write(text) = msg {
println!("메시지: {}", text);
}
  • if let 문법을 활용해 관심 있는 variant 한 가지만 매칭해 변수 바인딩 후 처리할 수 있습니다.

다. 여러 variant를 한 패턴에 묶기

match msg {
Message::Quit | Message::Write(_) => println!("Quit 또는 Write variant"),
_ => println!("다른 variant"),
}
  • 여러 variant를 하나로 합쳐 처리할 수도 있습니다.

3. 패턴 바인딩 시 참조와 가변성

  • 기본적으로 match 패턴 내 변수 바인딩은 값 소유권을 가져오거나 복사하지만, 참조 및 가변 참조로도 바인딩할 수 있습니다.
let p = Point { x: 10, y: 20, z: 0 };

match &p { // 불변 참조 패턴 매칭
Point { x, y, .. } => println!("x={}, y={}", x, y),
}

let mut p2 = Point { x: 0, y: 0, z: 0 };

match &mut p2 { // 가변 참조 패턴 매칭
Point { x, y, .. } => {
*x += 1;
*y += 1;
println!("수정된 값 x={}, y={}", x, y);
}
}
  • 참조에 따라 변수 바인딩 시 가변성 여부가 달라지고, 이를 통해 구조체 또는 열거형 내부 값을 안전하게 변경할 수 있습니다.

4. 변수 이름 재사용(shadowing)과 패턴 매칭

패턴 매칭 시, 이전에 바인딩된 변수 이름과 동일한 이름을 써도 새로운 바인딩이 생성됩니다.

let x = 5;
let p = Point { x: 10, y: 20, z: 30 };

match p {
Point { x, y, .. } => {
// 여기 x는 패턴 내의 새 바인딩, 밖의 x와 다른 변수
println!("패턴 매칭 내의 x: {}, 외부 x: {}", x, 5);
}
}

5. 요약 정리

기능예시 (일부만)설명
구조체 부분 패턴 매칭Point { x, .. }특정 필드만 분해, 나머지는 무시
가변 참조 매칭match &mut p와 Point { x, y, .. }구조체 필드를 가변 참조로 바인딩해 값 수정 가능
열거형 가변 매칭match &mut msg와 Message::Move { x, y }enum 내부 필드를 가변 참조로 바인딩해 값 수정 가능
if let 구문if let Message::Write(text) = msg특정 variant만 골라 간단히 바인딩 후 처리
패턴 내 변수 이름 재사용Point { x, y } 내 x는새로운 바인딩기존 변수와 이름이 동일해도 새 변수로 처리

이상으로 구조체와 열거형에서 변수 바인딩 시 사용하는 다양한 패턴 매칭 기법과 가변 바인딩 방법에 대해 설명드렸습니다.

패턴 매칭과 제어 흐름 심화 (match, if let, while let)

Rust는 패턴 매칭을 통해 다양한 값 구조를 해체하고 조건에 따라 분기할 수 있도록 지원합니다. match, if let과 while let은 이를 위해 자주 사용하는 문법입니다.


1. 복습: match 표현식

enum Coin {
    Penny,
    Nickel,
    Dime,
    Quarter,
}

fn value_in_cents(coin: Coin) -> u8 {
    match coin {
        Coin::Penny => 1,
        Coin::Nickel => 5,
        Coin::Dime => 10,
        Coin::Quarter => 25,
    }
}
  • enum은 Coin의 종류를 열거하고 있습니다.
  • match는 모든 경우를 명시해야 합니다. 위 경우 Coin의 종류 4개를 모두 다루고 있습니다.
  • 컴파일러가 exhaustiveness(완전성 – 모든 경우를 다룸)을 검사합니다.
  • 함수에서 coin을 인수로 받는데, type은 Coin 열거형이고, 반환 type은 u8로 0과 양수입니다.
  • 열거형을 분기할 때 열거형의 이름에 variant(변형)을 ::으로 연결하며,
    반환 값은 => 다음에 적고, 각각의 경우를 ,로 구분합니다.

위 코드를 실행하려면 main 함수가 아래와 같이 필요합니다.

fn main() {
    let coin1 = Coin::Penny;
    println!("{} 센트", value_in_cents(coin1));
}

Coin의 종류를 Coin:: 다음에 입력하면 value_in_cents 함수에 의해 해당하는 센트를 표시합니다.

{}가 값이 출력될 위치(placeholder)이고, 값은 value_in_cents(coin1)으로 구합니다.


2. 패턴 바인딩

enum Message {
    Hello { id: i32 },
}

fn main() {
    let msg = Message::Hello { id: 42 };

    match msg {
        Message::Hello { id: 1 } => println!("ID는 1"),
        Message::Hello { id } => println!("다른 ID: {}", id),
    }
}
  • Message 열거형은 Hello 구조체를 변형으로 갖으므로 id에 해당하는 정수를 값으로 받습니다.
  • let msg = Message::Hello { id: 42 };
    : Message 열거형으로 Hello 구조체를 만드는데 id는 42입니다.
  • 이 패턴은 Hello { id: i32 }에 매칭되며, id 값을 꺼내서 변수 id에 바인딩합니다.
  • 따라서, 두번째 분기만 있어도 되는데, 1인 경우를 별도의 경우로 빼낸 것입니다.
    다시 말해, 특정 값 비교와 변수 추출을 동시에 할 수 있습니다.

3. 와일드카드와 _, |, .., ..=

fn main() {
    let x = 7;

    match x {
        1 => println!("하나"),
        2 | 3 => println!("둘 또는 셋"),
        4..=6 => println!("4에서 6 사이"),
        _ => println!("기타"),
    }
}
  • |: 여러 패턴 매칭(or).
    위 경우 2 | 3은 2또는 3인 경우가 됩니다.
  • ..=6: 범위 매칭(=은 포함, 없으면 미만).
    위 경우 4..=6은 4이상 6이하가 되며, 4..6은 4이상 6미만이므로 4와 5만 해당됩니다.
  • _: 나머지 모든 경우 (wildcard, else)
  • x의 데이터 형식은 정수이므로 i32가 돼서 부호있는 32비트 정수이므로 -2^31 (약 -21억)부터 2^31 – 1 (약 21억)까지의 값을 표현 가능
  • 위 코드를 실행하면 x가 7이므로 _에 해당되어 “기타”가 출력됩니다.

4. if let 표현식

match가 너무 장황할 때 if let을 사용해 간단히 표현할 수 있습니다.

let some_value = Some(5);

if let Some(x) = some_value {
    println!("값은 {}", x);
} else {
    println!("값이 없습니다");
}
  • Some은 Option enum의 variant중 하나로서, if let을 이용해서 match의 단일 분기를 간단히 표현한 것이며,
    match 연산자를 이용하면 아래와 같이 해야 됩니다.
    match some_value {
        Some(x) => println!("값은 {}", x),
        None => println!("값이 없습니다"),
    }
  • if let Some(x) = some_value는 “x가 5라면”이 되므로, “값은 5″가 출력됩니다.
  • match 연산자의 경우는 None을 사용하는데, if 제어문이라 else를 사용했는데 없어도 됩니다.

5. while let 반복문

let mut stack = vec![1, 2, 3];

while let Some(top) = stack.pop() {
    println!("꺼낸 값: {}", top);
}
  • vec은 아직 설명하지 않았는데, array가 크기가 고정되어 있다면 vec은 가변 길이의 배열입니다.
  • while let을 이용하면 값이 존재하는 동안 반복합니다.
  • Option, Result와 같이 반복 가능한 열거형에 유용
  • 위 코드를 main 함수에 넣고 실행하면 아래와 같이 출력됩니다.

🧠 요약

문법설명
match여러 경우를 완전하게 분기
바인딩패턴 내부에서 변수에 값 대입 가능
_기타 모든 경우 처리
if let한 가지 매칭에 적합한 축약형
while let값이 남아 있는 동안 반복