학습 결과: 예상한 값 부재와 복구 가능한 실패를 값으로 표현하고, 범위가 끝날 때의 정리를 보장하며, 둘을 런타임 계약 위반과 구분합니다.
선행 학습: 데이터와 제어를 마치고 값이 if, match, for를 통과하는 흐름을 따라갈 수 있어야 합니다.
잘못된 입력을 예상한 결과로 만들기
failures-resources.tpz를 만드세요.
record StudyTask {
title: string,
minutes: int,
}
function parseMinutes(text: string) -> Result<int, string> {
match toInt(text) {
case Some(value) if value > 0 => Ok(value)
case _ => Err("minutes must be a positive integer")
}
}
function makeTask(title: string, minuteText: string) -> Result<StudyTask, string> {
let minutes = parseMinutes(minuteText)?
Ok(StudyTask { title: title, minutes: minutes })
}
function inspect(minuteText: string) -> () {
print("input: {minuteText}")
defer { print("inspection finished") }
match makeTask("Build application", minuteText) {
case Ok(task) => print("ready: {task.title}, {task.minutes} minutes")
case Err(message) => print("cannot add task: {message}")
}
}
inspect("25")
inspect("later")검사하고 실행합니다.
topaz check failures-resources.tpz
topaz run failures-resources.tpz
잘못된 문자열도 데이터로 처리하므로 프로세스는 정상적으로 끝납니다.
input: 25
ready: Build application, 25 minutes
inspection finished
input: later
cannot add task: minutes must be a positive integer
inspection finishedtoInt는 Option을 반환합니다. Some(value)는 정수 값이 있음을 뜻하고, 나머지 경우는 값이 없음을 나타냅니다. parseMinutes는 값 부재와 0 이하의 입력을 설명이 있는 Result로 바꿉니다.
makeTask도 Result를 반환합니다. 후위 ?는 Ok 안의 값을 꺼내고, Err이면 호환되는 현재 함수의 결과로 즉시 전달합니다. 따라서 잘못된 작업은 만들어지지 않습니다. inspect는 두 결과를 명시적으로 처리합니다.
defer는 검사를 시작한 범위에 속합니다. 성공한 입력과 실패한 입력은 서로 다른 갈래를 지나지만, 두 경우 모두 마지막에 정리 문구가 나타납니다. 실제 리소스를 소유하는 함수에서도 반드시 해야 하는 정리는 이처럼 소유 범위에 둡니다.
깨진 계약 살펴보기
이제 intentional-fault.tpz를 만드세요.
let tasks = ["Run first program", "Build application"]
print(tasks[2])topaz run intentional-fault.tpz를 실행하면 0이 아닌 코드로 끝납니다.
error[TPZ4001]: index 2 is out of bounds for an array of length 2
--> intentional-fault.tpz:2:7
|
2 | print(tasks[2])
| ^^^^^^^^
이것은 복구할 Err이 아니라 fault입니다. 프로그램이 배열 경계 계약을 어겼기 때문입니다. 진단은 임의의 값을 만들어 내지 않고 깨진 계약과 소스 위치를 알려 줍니다.
선택: 값 부재, 복구, 계약 위반
값이 없는 것이 정상적인 결과라면 Option을 사용하세요. 호출자가 의미 있게 대응할 수 있는 오류에는 Result를 쓰고, 호환되는 다른 Result로 전달할 때 ?를 사용합니다. 깨진 계약에는 fault가 맞습니다. 이 선택과 별개로, 범위를 빠져나갈 때 반드시 정리해야 한다면 defer를 사용합니다.
직접 해보기
정상 프로그램에 inspect("0")을 추가하세요. 어느 갈래가 실행되며, 지연된 정리 문구도 나타날까요?
정답 보기
0은 가드 조건을 통과하지 못하므로 cannot add task: minutes must be a positive integer가 출력됩니다. inspect 범위를 나갈 때 defer가 실행되므로 다음 줄에는 여전히 inspection finished가 나타납니다.
다음 단계로 넘어갈 준비
잘못된 문자열이 Err가 되는 이유, makeTask 안에서 ?가 안전한 이유, 정리 문구가 두 번 나타나는 이유, 범위를 벗어난 접근이 예상 입력이 아니라 fault인 이유를 설명할 수 있다면 준비되었습니다.
정확한 규칙은 Null·Option·결과·실행 오류와 Defer와 리소스에서 확인하세요. 첫 애플리케이션으로 계속하세요.