프로그램에 맞는 데이터 형식을 고르고, 필드가 같아도 타입이 다른 경우를 구분해 보세요. 예제에서는 사용자를 만든 뒤 원본을 유지하면서 상태를 바꿉니다.
첫 프로그램을 따라 토파즈를 설치하세요. 레코드를 처음 쓴다면 데이터와 제어부터 시작하세요.
사용자를 만들고 갱신하기
작업 폴더에 다음 코드를 records-nominal-data.tpz로 저장하세요.
type 표시값 = int | string
newtype 사용자번호 = int
enum 상태 {
활성,
일시정지(string),
}
record 사용자 {
번호: 사용자번호,
이름: string,
상태값: 상태 = 상태.활성,
}
function 상태표시(상태값: 상태) -> string {
match 상태값 {
case 활성 => "active"
case 일시정지(이유) => 이유
}
}
function 표시(값: 표시값) -> string {
match 값 {
case 숫자: int => "number {숫자}"
case 텍스트: string => 텍스트
}
}
let 이전: 사용자 = 사용자 { 번호: 사용자번호(7), 이름: "Ada" }
let 이후: 사용자 = 사용자 { ...이전, 상태값: 상태.일시정지("review") }
let 미리보기 = { 이름: "Ada", 나이: 36 }
let 나이든 = 미리보기{ 나이: 37 }
let 준비 = 표시("ready")
print("{이전.번호.value()}:{이전.이름}:{상태표시(이전.상태값)}")
print("{이후.이름}:{상태표시(이후.상태값)}")
print("{미리보기.나이}:{나이든.나이}")
print("{표시(7)}:{준비}")같은 폴더에서 파일을 검사하고 실행하세요.
topaz check records-nominal-data.tpz
topaz run records-nominal-data.tpz검사는 종료 코드 0으로 성공합니다. 실행하면 다음 결과를 출력합니다.
7:Ada:active
Ada:review
36:37
number 7:ready결과 읽기
이전은 기본 상태인 활성을 가집니다. 이후는 번호와 이름을 유지하고 상태를 일시정지("review")로 바꿉니다. 이 갱신은 이전을 바꾸지 않고 기본값도 다시 계산하지 않습니다.
미리보기는 이름 있는 레코드 선언 없이 필드 두 개를 묶습니다. 나이를 갱신하면 나이든을 새로 만들므로 세 번째 줄은 36:37을 출력합니다.
표시값은 유니온에 붙인 별칭입니다. 값을 감싸지 않고 int나 string을 받습니다. 표시의 타입 패턴은 둘 중 해당하는 타입을 고르고 값에 이름을 붙입니다.
필드와 선언을 구분하기
구조적 레코드는 필드의 이름과 타입에 따라 호환됩니다. 예를 들어 미리보기의 구조는 { 이름: string, 나이: int }입니다.
명목 레코드는 선언으로 구분하는 타입입니다. 사용자와 별도로 선언한 고객은 필드가 같아도 서로 다른 타입입니다. 같은 필드를 가진 구조적 레코드도 사용자 자리에 쓸 수 없습니다.
newtype 사용자번호 = int는 정수를 감싸는 별도 타입을 만듭니다. 사용자번호(7)로 값을 만들고 .value()로 안의 정수를 읽습니다. 한 번 호출할 때 한 겹씩 벗깁니다.
| 필요한 값 | 선택 | 예제 |
|---|---|---|
| 필드 구성으로 구분하는 값 | 구조적 레코드 | 미리보기 |
| 이름 있는 필드를 가진 별도 타입 | 명목 record | 사용자 |
| 선언한 변형 가운데 하나 | enum | 상태.활성 또는 상태.일시정지(이유) |
| 여러 기존 타입 가운데 하나인 값 | 유니온 A | B | 표시값 |
| 기반 값 하나를 감싸는 별도 타입 | newtype | 사용자번호 |
열거형의 변형은 선언 본문에서 정합니다. 상태.일시정지("review")로 만들고 case 일시정지(이유)로 검사합니다. 검사기는 열거형의 모든 변형을 처리하는지 확인합니다. 가드는 분기에 덧붙이는 조건입니다. 가드가 있는 분기는 전체 변형을 처리했다는 판단에 포함하지 않습니다.
필드 생성과 갱신
토파즈는 필드 이름과 타입을 검사합니다. 명시한 필드는 왼쪽부터 계산합니다. 그다음 생략한 필드의 기본값을 선언 순서대로 한 번씩 계산합니다.
명목 레코드를 갱신하려면 맨 앞에 ...source 하나를 쓰고 바꿀 필드를 적으세요. 원본은 같은 레코드 선언과 같은 제네릭 타입 인수를 가져야 합니다. 토파즈는 원본을 먼저 계산합니다. 이후 대체 필드를 왼쪽부터 계산해 해당 필드 값을 덮어씁니다.
갱신은 바깥 레코드를 복사합니다. 필드에 든 배열처럼 변경 가능한 값은 원본과 결과가 함께 사용합니다.
외부 프로그램이 레코드를 전달하면 실행 중에도 선언된 필드를 정확히 갖췄는지 검사합니다. 이 검사는 대체 표현식을 계산하기 전에 수행합니다.
| 잘못된 입력 레코드 | 실행 오류 |
|---|---|
| 필드 누락 또는 중복 | GUARD_ARITY (TPZ5004) |
| 알 수 없는 필드 | GUARD_NO_FIELD (TPZ5006) |
패턴 검사와 실수 고치기
case 사용자 { 이름 } =>는 선언을 확인하고 이름을 읽습니다. 적지 않은 필드는 무시합니다. 필드를 하나 이상 지정하고 레코드 이름에는 네임스페이스를 붙이지 마세요.
예제의 구조적 레코드는 미리보기{ 나이: 37 }로 갱신하세요. 이름 있는 레코드는 사용자 { ...이전, 상태값: 상태.일시정지("review") }로 갱신하세요. JavaScript식 { ...미리보기 }는 토파즈에서 쓸 수 없습니다.
메서드는 모듈 최상위의 별도 impl 사용자 블록에 두세요. self는 값으로 전달받으며 변경할 수 없습니다. 레코드 본문에는 암시적인 this, &self, &mut self가 없습니다.
다른 데이터 규칙
- 레코드·열거형·뉴타입은 모듈 최상위에 선언합니다.
- 제네릭 명목 타입은 불변이므로 타입 인수를 바꾸면 다른 타입이 됩니다. 값을 만들 때 필요한 기대 타입은 타입에서 설명합니다.
- 동등성 비교·순서 비교·컬렉션 키는 내용과 선언을 함께 구분합니다. 내부 타입도 해당 연산을 지원해야 합니다.
- 리플렉션·상속·추가 필드 구성을 열어 두는 레코드 타입은 지원하지 않습니다. 명목 패턴에는 선언된 필드를 씁니다.
- 네임스페이스를 붙인 레코드·뉴타입 생성, 실행 중 프로토콜 구현을 고르는 동적 디스패치, Rust 소유권식 수신자는 지원하지 않습니다.
분기를 고르고 값을 꺼내는 방법은 패턴과 제어 흐름에서 이어집니다.