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

구조체(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는새로운 바인딩기존 변수와 이름이 동일해도 새 변수로 처리

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

Rust의 상수와 복합 타입 (Compound Types)

Scalar Types에 대해서는 아래 글에서 살펴봤는데, 이번에는 상수와 복합 타입(Compound Types) 튜플과 배열을 살펴보겠습니다. 상수는 변하지 않는 값이며, 튜플은 다양한 타입의 값을 하나의 그룹으로 묶은 것이고, 배열은 동일한 타입의 값을 고정된 크기로 저장합니다.


🔷 상수 (Constants)

상수는 프로그램 전체에서 변하지 않는 값을 나타냅니다.

const MAX_POINTS: u32 = 100_000;

fn main() {
    println!("최대 점수: {}", MAX_POINTS);
}
  • 변수는 let 키워드를 사용하는데, 상수는 const 키워드를 사용하며 함수 외부/내부 모두 선언 가능
  • 타입을 반드시 명시해야 하며(추론 적용되지 않음), 대문자+언더스코어로 표기하는 것이 관례입니다.
  • 위 코드 중 100_000에서 _는 천 단위 구분 기호인 ,를 대체하는 기호입니다.
  • const는 컴파일 타임에 값이 결정됨

참고: let로 선언한 변수는 mut로 변경 가능하지만, const는 절대 변경되지 않습니다.

fn main() {
    const MAX_POINTS: u32 = 100_000;
    MAX_POINTS += 10; // 상수는 변경할 수 없음, 이 줄은 오류를 발생시킴
    println!("최대 점수: {}", MAX_POINTS);
}

MAX_POINTS를 변경하려고 하면 값을 할당할 수 없다는 에러가 발생합니다.


🔷 튜플 (Tuples)

튜플은 다양한 타입의 값을 하나의 그룹으로 묶습니다.

fn main() {
    let person: (&str, u32) = ("Alice", 30);
    let (name, age) = person;

    println!("이름: {}, 나이: {}", name, age);
    println!("튜플 직접 접근: {}", person.0);
}
  • 괄호 안에 콤마로 구분되는 값들의 목록을 작성하여 튜플을 만듭니다.
  • 고정된 길이와 순서를 가지며, 서로 다른 타입 허용
    위 예에서는 &str, 다시 말해 스트링 슬라이스와 u32 정수 타입이 섞여 있습니다.
    타입을 입력하지 않으면 추론되는데, 정수는 i32가 기본 타입이므로 i32로 추론됩니다.
  • . 문법으로 인덱스로 튜플의 요소에 접근 가능
    위 예에서 person.0은 첫번째 값인 이름을 가리키고, .1을 하면 나이를 가리키게 됩니다.

튜플의 구조해체(destructuring)

튜플의 속성인 그룹을 해체하여 각각의 값을 개별 변수에 할당하는 것을 말합니다.

위 예에서 let (name, age) = person; 란 구문을 사용했는데,
person이란 튜플의 첫번째 요소는 name에, 두번째 요소는 age 변수에 할당하는 것입니다.
다시 말해 튜플은 구조해체 또는 .인덱스를 이용해 요소에 접근할 수 있습니다.


🔷 배열 (Arrays)

배열은 동일한 타입의 값고정된 크기로 저장합니다.

fn main() {
    let scores: [i32; 3] = [90, 85, 78];

    println!("첫 번째 점수: {}", scores[0]);

    for score in scores.iter() {
        println!("점수: {}", score);
    }
}
  • 대괄호 안에 값들을 콤마로 구분하여 나열해서 배열을 만듭니다.
  • [i32; 3]와 같이 타입 뒤에 ;(:이 아님)을 붙이고 숫자를 쓰면, i32 타입 3개의 배열 의미
  • let scores = [30; 10]; 이라고 입력하면 scores 배열에 정수 30을 10개 입력한 것이 됩니다.
  • scores[0]처럼 대괄호안에 인덱스를 입력하여 배열의 요소에 접근 가능
  • for와 .iter()를 이용해서 반복 가능

배열(Array)과 벡터(vector)

배열이 유용할 때는 항상 고정된 크기의 요소를 갖는데 비해서 벡터 타입은 유사 집합체로 표준 라이브러리에서 제공되며 크기를 확장 또는 축소가 가능합니다. 배열이나 벡터 중에 뭘 선택해야 할지 확실하지 않은 상황이라면 벡터를 사용하라고 합니다.

유효하지 않은 배열 요소에 대한 접근

아래에서 a배열의 가장 큰 인덱스가 4인데, 10으로 지정하고 cargo run을 하면

fn main() {
    let a = [1, 2, 3, 4, 5];

    let element = a[10];

    println!("The value of element is: {}", element);
}

아래와 같이 길이가 5인데, 인덱스가 10이라는 경계를 벗어난 인덱스 에러가 발행합니다.


🧠 요약

항목설명
const변경 불가능한 상수, 타입 명시 필수
튜플다양한 타입을 그룹화, 순서 중요
배열동일한 타입, 고정된 크기, 인덱스로 접근