프로그램 구조

Null·Option·결과·fault

값이 없는 정상 상황, null을 허용하는 외부 데이터, 복구 가능한 실패, 런타임 계약 위반을 각각 어떻게 표현할지 정리합니다.

값의 부재 가능성이나 작업 실패 가능성을 다룰 때는 이 페이지를 참조하세요. 먼저 검토해야 할 질문은 “어느 연산자가 더 짧은가?”가 아니라 “호출자가 처리를 계속 진행할 수 있는가?”입니다.

의미에 맞춰 선택하기

상황사용할 형식
토파즈 내부에서 값이 없는 상태가 정상 결과인 경우Some(value) 또는 None을 담는 Option<T>
외부 데이터에 명시적인 null 허용 필드가 포함된 경우T | null
호출자가 오류 원인을 확인하고 대응할 수 있는 경우Ok(value) 또는 Err(error)를 담는 Result<T, E>
프로그램이 런타임 계약을 위반한 경우fault. 값을 생성하지 않고 실행을 중단함

토파즈 API에서 값의 부재가 정상 상태에 해당한다면 Option으로 표현하세요. null이 실제로 들어 있는 데이터와의 상호 운용 경계에만 null을 남겨 두세요. 호출자가 실패 원인을 알아야 하거나 복구 여부를 판단해야 하는 경우에는 Result를 사용합니다.

잘못된 입력을 복구 가능한 결과로 바꾸기

다음은 그대로 실행되는 완전한 프로그램입니다. toInt가 값을 생성하지 못한 상황을 오류 원인이 담긴 Result로 변환합니다.

TOPAZ
record 학습과제 {
    제목: string,
: int,
}

function 분해석(텍스트: string) -> Result<int, string> {
    match toInt(텍스트) {
        case Some() if > 0 => Ok()
        case _ => Err("minutes must be a positive integer")
    }
}

function 과제만들기(제목: string, 분문자열: string) -> Result<학습과제, string> {
    let = 분해석(분문자열)?
    Ok(학습과제 { 제목: 제목, :  })
}

function 검사(분문자열: string) -> () {
    print("input: {분문자열}")
    defer { print("inspection finished") }
    match 과제만들기("Build application", 분문자열) {
        case Ok(과제) => print("ready: {과제.제목}, {과제.} minutes")
        case Err(메시지) => print("cannot add task: {메시지}")
    }
}

검사("25")
검사("later")

검사하고 실행합니다.

BASH
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 finished

정수로 해석할 수 없는 텍스트는 값을 생성하지 못합니다. 따라서 toIntOption<int>를 반환합니다. 분해석은 값의 부재를 호출자가 다루어야 한다고 판단하여 이를 Err로 변환합니다. 과제만들기의 후위 ?Ok 내부의 값을 추출합니다. Err를 만나면 해당 오류를 함수 외부로 즉시 반환하며, 이때 감싸는 함수의 반환 타입은 해당 오류를 담을 수 있는 Result여야 합니다. 마지막 match는 두 결과를 각각 직접 처리합니다.

선택 값과 null 허용 값 다루기

선택적 접근과 기본값 연산은 값이 없는 두 표현 방식을 서로 섞어서 다루지 않습니다.

  • maybeUser: Option<{ name: string }>이면 maybeUser?.name의 결과도 Option입니다. maybeUser?.name ?? "guest"string을 생성합니다.
  • configPath: string | null이면 configPath ?? "default.toml"은 값이 null일 때만 기본값을 사용합니다.
  • target ??= value는 타깃이 비어 있을 때만 대입하는 구문입니다. Option<int> 타깃에는 42가 아니라 Some(42)가 필요합니다. 토파즈는 Some을 자동으로 감싸지 않습니다.

?.??는 모두 좌항을 한 번만 평가합니다. ?.은 수신자가 비어 있으면 메서드 인자도 평가하지 않습니다. 각 연산자는 선택 레이어를 한 겹만 해제하거나 평탄화하며, Option을 임의로 null로 변환하지 않습니다.

흔한 실수 바로잡기. 값이 선택형이나 null 허용형이 아니라면 일반적인 .을 사용하세요. 타입에 Option<T>null이 함께 포함되어 있다면 단축 연산자를 중첩해 쓰지 마세요. 각 경우를 match로 명시적으로 구분하는 편이 낫습니다.

fault는 오류 값이 아닙니다

다음 프로그램은 배열의 범위를 벗어난 원소를 꺼내려 합니다.

TOPAZ
let 과제들 = ["Run first program", "Build application"]
print(과제들[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(과제들[2])
  |       ^^^^^^

범위 위반은 fault입니다. fault는 실행을 그 자리에서 멈추며 None이나 Err 같은 값을 생성하지 않습니다. 후위 ?, ??, 선택 체인, concurrent는 fault를 포착하거나 다른 값으로 변환하지 않습니다. 잘못된 상수 연산처럼 검사 시점에 미리 알 수 있는 형태도 있습니다. 이러한 코드는 실행 전에 정적 오류로 거부될 수 있습니다.

정확한 경계

  • 후위 ?는 동일한 오류 값을 담을 수 있는 반환 타입을 가진 함수나 클로저 내부에서만 사용할 수 있습니다.
  • 일반적인 try, 예외, fault 포착, panic 키워드, 자동 재시도는 현재 토파즈 형식에 포함되지 않습니다.
  • assert(...)test-profile을 명시적으로 선택한 코드에서만 쓸 수 있습니다. 일반 프로그램용 단언문이 아닙니다.
  • 스코프가 정상적으로 종료될 때의 정리는 defer가 담당합니다. fault가 발생했을 때는 정리 경계가 더 좁습니다. 따라서 fault를 복구 가능한 Result처럼 다루어선 안 됩니다.

따라 하는 연습은 실패와 리소스에서 할 수 있습니다. 정리 시점은 Defer와 리소스에서 다룹니다. 런타임 fault 경계는 런타임 동작에서 이어서 확인하세요.