rokdocs
v0.1

3.15. 분해 대입 (destructuring)

let 의 좌변에 패턴 의 반증 불가능 패턴을 적으면, 한 식의 결과를 여러 변수에 풀어 받을 수 있습니다.

3.15.1. 형태

분해 대입의 문법은 다음과 같이 정의됩니다.

destructure_let ::= "let" irrefutable_pattern (":" type_expr)? "=" expr ";"
let 패턴 = 식;

이 형태는 식의 결과를 패턴에 매칭하여 패턴 안의 바인딩 변수들을 한꺼번에 선언하는 역할을 합니다.

3.15.2. 튜플 분해

가장 기본적인 형태인 튜플 분해는 아래 예시처럼 사용할 수 있습니다.

let (a, b) = (1, 2);                    // a = 1, b = 2
let (q, r) = divmod(10, 3);             // 두 값을 한 번에 받음
let (q, _) = divmod(10, 3);             // 두 번째는 폐기

이때 매칭 대상의 항 수가 정확히 일치해야 하며, 다르면 컴파일 에러가 발생합니다.

let (a, b) = (1, 2, 3);                 // ERROR: 길이 불일치

3.15.3. rest 패턴

나머지 항들을 한꺼번에 묶어서 받고 싶다면 .. 를 사용한 rest 패턴을 활용할 수 있습니다.

let (first, ..rest) = (1, 2, 3, 4);     // rest = (2, 3, 4)
let (..init, last) = (1, 2, 3, 4);      // init = (1, 2, 3), last = 4
let (head, ..mid, tail) = (1, 2, 3, 4, 5);  // mid = (2, 3, 4)
let (..) = (1, 2, 3);                    // 전부 폐기

.. 는 한 패턴 안에서 한 번만 등장하며, 나머지를 묶어 한 변수에 바인딩하거나 그대로 폐기하는 데에 쓰입니다.

3.15.4. 중첩 분해

분해 패턴은 임의의 깊이로 중첩할 수 있어, 복잡한 구조도 한 번에 풀어낼 수 있습니다.

let ((a, b), c) = ((1, 2), 3);          // a = 1, b = 2, c = 3
let (Point { x, y }, scale) = (origin, 2.0f64);

분해되는 모양이 매칭 대상의 모양과 구조적으로 일치하기만 하면 되므로, 표현의 자유도가 높습니다.

3.15.5. 구조체 분해

구조체 값에 대해서는 패턴 의 struct 패턴을 그대로 활용할 수 있습니다.

let Point { x, y } = origin;            // x = origin.x, y = origin.y
let Point { x, .. } = origin;           // 다른 필드 폐기
let Point { x: px, y: py } = origin;    // 재명명

이를 통해 필드를 한꺼번에 추출하거나, 일부 필드만 받고 나머지는 폐기하거나, 새로운 이름으로 재명명할 수 있습니다.

3.15.6. 타입 명시

분해의 각 부분에 타입을 명시할 수도 있고, 전체 패턴에 한꺼번에 명시할 수도 있습니다.

let (a: i32, b: str) = (1i32, "hi");    // 각 자리에 타입 명시
let (a, b): (i32, str) = (1i32, "hi");  // 전체 패턴에 타입 명시

명시한 타입은 슬롯 타입이며 런타임 타입 검사가 아닙니다. 매칭 대상의 타입과 일치해야 하며, 다르면 컴파일 에러가 발생합니다.

let (a: i32, b: str) = (1, 2);          // ERROR: 두 번째 타입 불일치

3.15.7. 반증 불가능 패턴만

let 패턴 = 식; 의 패턴은 반증 불가능이어야 합니다. 이는 항상 매칭되는 형태여야 한다는 뜻입니다.

허용되는 패턴은 다음과 같습니다.

  • 식별자, 와일드카드.
  • 튜플, 구조체, tuple-struct 의 완전 매칭 (필드나 위치 누락 없이).
  • 위 형태들의 중첩.

반면, 다음과 같은 패턴은 허용되지 않습니다.

  • 리터럴 패턴 (1, "hello" 등).
  • enum 의 특정 변형만 매칭하는 패턴 (Circle(r), Click { x, y } 등).
  • 타입 검사 패턴 (v: i32, 합 타입이나 any 슬롯에서).
  • 범위 패턴.

이러한 형태들은 매칭이 실패할 수 있어서 let 으로 받기에 안전하지 않습니다. 이때는 let-else, match 표현식, if let (if 표현식) 을 사용합니다.

3.15.8. 기본값 (없음)

분해 자리의 기본값은 도입하지 않습니다.

let (a, b = 0i32) = ...;        // 채택 안 함

함수 매개변수의 기본값으로 충분하다고 판단했기 때문입니다. 분해 자리에서 없을 수 있는 값을 처리해야 한다면, match 나 let-else 를 활용하면 됩니다.

3.15.9. 분해 후의 변수 가시성

분해로 선언된 모든 변수는 let 이 등장한 스코프에 도입됩니다.

let (a, b) = (1, 2);
// 여기서 a, b 모두 가시

같은 let 으로 선언된 변수들은 동시에 가시가 되며, 어느 한쪽이 먼저 가시 되는 일은 없습니다.

3.15.10. 매칭 식의 평가 횟수

분해의 우변 식은 한 번만 평가되며, 평가 결과가 패턴에 매칭됩니다. 따라서 부작용이 있는 식이 한 번만 호출됨이 보장됩니다.

3.15.11. 다중 대입의 일반화

분해 대입은 다중 대입의 일반화로 볼 수 있습니다.

let (a, b) = (b, a);                    // swap, 한 식으로
let (q, r) = divmod(num, den);          // 두 결과
let (head, ..tail) = items;             // 분리

이러한 형태 덕분에 C 나 Python 식의 a, b = b, a 패턴도 자연스럽게 표현할 수 있습니다.

3.15.12. let 없이: 재할당의 분해 (없음)

기존 변수에 대한 분해 재할당은 도입하지 않습니다.

(a, b) = (1, 2);                // 채택 안 함

let 이 항상 필요하며, 이는 새 선언인지 재할당인지의 구분을 명확히 하기 위함입니다 (대입 의 결정과 일관됩니다).

기존 변수에 분해 결과를 대입하려면 임시 변수를 거치거나 명시적으로 분리하여 처리하면 됩니다.

GitHub에서 이 페이지 고치기