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 이 항상 필요하며, 이는 새 선언인지 재할당인지의 구분을 명확히 하기 위함입니다 (대입 의 결정과 일관됩니다).
기존 변수에 분해 결과를 대입하려면 임시 변수를 거치거나 명시적으로 분리하여 처리하면 됩니다.